From 7894562e0c8a3b982fb8c7c7b12f9f4cb1356452 Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Mon, 4 Apr 2022 13:24:30 +0000 Subject: [PATCH 01/46] v2 of getSmartContractInfo --- ranchimallflo_api.py | 483 +++++++++++++++++++++++++++++++------------ 1 file changed, 353 insertions(+), 130 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index d35e77a..6fbb6e3 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -2,12 +2,12 @@ from collections import defaultdict import sqlite3 import json import os -import requests -import sys -import time -from datetime import datetime -from quart import jsonify, make_response, Quart, render_template, request, flash, redirect, url_for -from quart_cors import cors +import requests +import sys +import time +from datetime import datetime +from quart import jsonify, make_response, Quart, render_template, request, flash, redirect, url_for +from quart_cors import cors import asyncio from typing import Optional @@ -19,7 +19,7 @@ import subprocess from apscheduler.schedulers.background import BackgroundScheduler import atexit - +import pdb app = Quart(__name__) app.clients = set() @@ -85,12 +85,21 @@ def transactiondetailhelper(transactionHash): conn = sqlite3.connect(os.path.join(dbfolder, 'latestCache.db')) c = conn.cursor() - c.execute( - f"select jsonData,parsedFloData from latestTransactions where transactionHash='{transactionHash}'") + c.execute(f"select jsonData, parsedFloData, transactionType, db_reference from latestTransactions where transactionHash='{transactionHash}'") transactionJsonData = c.fetchall() return transactionJsonData + +def smartContractInfo_output(contractName, contractAddress, contractType, subtype): + if contractType == 'continuos-event' and contractType == 'tokenswap': + pass + elif contractType == 'one-time-event' and contractType == 'userchoice': + pass + elif contractType == 'one-time-event' and contractType == 'timetrigger': + pass + + # FLO TOKEN APIs @app.route('/api/v1.0/broadcastTx/') async def broadcastTx(raw_transaction_hash): @@ -516,16 +525,14 @@ async def getContractInfo(): if contractAddress is None: return jsonify(result='error', description='Smart Contract\'s address hasn\'t been passed') - contractDbName = '{}-{}.db'.format(contractName.strip(), - contractAddress.strip()) + 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') + c.execute('SELECT attribute,value FROM contractstructure') result = c.fetchall() contractStructure = {} @@ -540,7 +547,7 @@ async def getContractInfo(): if len(conditionDict) > 0: contractStructure['exitconditions'] = conditionDict del counter, conditionDict - + returnval = contractStructure returnval['userChoice'] = contractStructure['exitconditions'] returnval.pop('exitconditions') @@ -556,8 +563,7 @@ async def getContractInfo(): 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())) + 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: @@ -598,20 +604,19 @@ async def getContractInfo(): 'select participantAddress, winningAmount from contractparticipants where winningAmount is not null') winnerparticipants = c.fetchall() - returnval['numberOfWinners'] = len( - winnerparticipants) + returnval['numberOfWinners'] = len(winnerparticipants) else: return jsonify(result='error', description='There is more than 1 trigger in the database for the smart contract. Please check your code, this shouldnt happen') - + return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, 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(): +@app.route('/api/v2.0/getSmartContractInfo', methods=['GET']) +async def getContractInfo2(): contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') @@ -621,16 +626,14 @@ async def getcontractparticipants(): if contractAddress is None: return jsonify(result='error', description='Smart Contract\'s address hasn\'t been passed') - contractDbName = '{}-{}.db'.format(contractName.strip(), - contractAddress.strip()) + 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') + c.execute('SELECT attribute,value FROM contractstructure') result = c.fetchall() contractStructure = {} @@ -646,20 +649,93 @@ async def getcontractparticipants(): contractStructure['exitconditions'] = conditionDict del counter, conditionDict + returnval = contractStructure + pdb.set_trace() + # Categorize into what type of contract it is right now + if contractStructure['contractType'] == 'continuos-event' and contractStructure['subtype'] == 'tokenswap': + c.execute('select count(participantAddress), sum(tokenAmount), sum(winningAmount) from contractparticipants') + participation_details = c.fetchall() + returnval['numberOfParticipants'] = participation_details[0][0] + returnval['totalParticipationAmount'] = participation_details[0][1] + returnval['totalHonorAmount'] = participation_details[0][2] + + c.execute('SELECT COUNT(DISTINCT transactionHash) FROM contractdeposits') + returnval['numberOfDeposits'] = c.fetchall()[0][0] + c.execute('SELECT COUNT(DISTINCT transactionHash) FROM contractdeposits') + returnval['numberOfDeposits'] = c.fetchall()[0][0] + conn.close() + + elif contractStructure['contractType'] == 'one-time-event' and 'exitconditions' in contractStructure.keys(): + returnval['userChoice'] = contractStructure['exitconditions'] + returnval.pop('exitconditions') + + 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] + + elif contractStructure['contractType'] == 'one-time-event' and 'payeeAddress' in contractStructure.keys(): + pass + + return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, 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', description='Smart Contract\'s name hasn\'t been passed') + + if contractAddress is None: + return jsonify(result='error', description='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() + + contractStructure = {} + conditionDict = {} + counter = 0 + for item in result: + 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 'exitconditions' in contractStructure: # contract is of the type external trigger # check if the contract has been closed - c.execute( - 'select * from contractTransactionHistory where transactionType="trigger"') + c.execute('select * from contractTransactionHistory where transactionType="trigger"') trigger = c.fetchall() if len(trigger) == 1: - c.execute( - 'select value from contractstructure where attribute="tokenIdentification"') + c.execute('select value from contractstructure where attribute="tokenIdentification"') token = c.fetchall() token = token[0][0] - c.execute( - 'SELECT id,participantAddress, tokenAmount, userChoice, transactionHash, winningAmount FROM contractparticipants') + c.execute('SELECT id,participantAddress, tokenAmount, userChoice, transactionHash, winningAmount FROM contractparticipants') result = c.fetchall() conn.close() returnval = {} @@ -667,20 +743,14 @@ async def getcontractparticipants(): returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4], 'winningAmount': row[5], 'tokenIdentification': token} - return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, - participantInfo=returnval) elif len(trigger) == 0: - c.execute( - 'SELECT id,participantAddress, tokenAmount, userChoice, transactionHash FROM contractparticipants') + c.execute('SELECT id,participantAddress, tokenAmount, userChoice, transactionHash FROM contractparticipants') result = c.fetchall() conn.close() returnval = {} for row in result: - returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], - 'transactionHash': row[4]} + returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4]} - return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, - participantInfo=returnval) else: return jsonify(result='error', description='More than 1 trigger present. This is unusual, please check your code') @@ -695,8 +765,24 @@ async def getcontractparticipants(): returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4]} - return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, - participantInfo=returnval) + elif contractStructure['contractType'] == 'continuos-event' and contractStructure['subtype'] == 'tokenswap': + pdb.set_trace() + c.execute('SELECT * FROM contractparticipants') + contract_participants = c.fetchall() + returnval = {} + for row in contract_participants: + returnval[row[1]] = { + 'participantFloAddress': row[1], + 'participationAmount': row[2], + 'swapPrice': float(row[3]), + 'transactionHash': row[4], + 'blockNumber': row[5], + 'blockHash': row[6], + 'swapAmount': row[7] + } + conn.close() + + return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, participantInfo=returnval) else: return jsonify(result='error', description='Smart Contract with the given name doesn\'t exist') @@ -714,89 +800,148 @@ async def getParticipantDetails(): if (contractName and contractAddress is None) or (contractName is None and contractAddress): return jsonify(result='error', description='pass both, contractName and contractAddress as url parameters') + #if os.path.isfile(dblocation) and os.path.isfile(contract_db): if os.path.isfile(dblocation): # Make db connection and fetch data conn = sqlite3.connect(dblocation) c = conn.cursor() - # Check if its a participant address - queryString = f"SELECT id, address,contractName, contractAddress, tokenAmount, transactionHash, blockNumber, blockHash FROM contractAddressMapping where address='{floAddress}' and addressType='participant'" - c.execute(queryString) - result = c.fetchall() - 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] - - c.execute( - f"select status, tokenIdentification, contractType, blockNumber, blockHash, incorporationDate, expiryDate, closeDate from activecontracts where contractName='{detailsDict['contractName']}' and contractAddress='{detailsDict['contractAddress']}'") - temp = c.fetchall() - detailsDict['status'] = temp[0][0] - detailsDict['tokenIdentification'] = temp[0][1] - detailsDict['contractType'] = temp[0][2] - detailsDict['blockNumber'] = temp[0][3] - detailsDict['blockHash'] = temp[0][4] - detailsDict['incorporationDate'] = temp[0][5] - if temp[0][6]: - detailsDict['expiryDate'] = temp[0][6] - if temp[0][7]: - detailsDict['closeDate'] = temp[0][7] - - # check if the contract has been closed - contractDbName = '{}-{}.db'.format(detailsDict['contractName'].strip(), - detailsDict['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() - - contractStructure = {} - conditionDict = {} - counter = 0 - for item in result: - 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 'exitconditions' in contractStructure: - # contract is of the type external trigger - # check if the contract has been closed - c.execute( - 'select * from contractTransactionHistory where transactionType="trigger"') - trigger = c.fetchall() - - if detailsDict['status'] == 'closed': - c.execute( - f"SELECT userChoice, winningAmount FROM contractparticipants where participantAddress='{floAddress}'") - result = c.fetchall() - conn.close() - detailsDict['userChoice'] = result[0][0] - detailsDict['winningAmount'] = result[0][1] - else: - c.execute( - f"SELECT userChoice FROM contractparticipants where participantAddress='{floAddress}'") - result = c.fetchall() - conn.close() - detailsDict['userChoice'] = result[0][0] - - participationDetailsList.append(detailsDict) - return jsonify(result='ok', floAddress=floAddress, type='participant', participatedContracts=participationDetailsList) + if contractName is not None: + c.execute(f'SELECT * FROM contractAddressMapping WHERE address="{floAddress}" AND addressType="participant" AND contractName="{contractName}" AND contractAddress="{contractAddress}"') else: - return jsonify(result='error', description='Address hasn\'t participanted in any other contract') + c.execute(f'SELECT * FROM contractAddressMapping WHERE address="{floAddress}" AND addressType="participant"') + participant_address_contracts = c.fetchall() + + if len(participant_address_contracts) != 0: + participationDetailsList = [] + for contract in participant_address_contracts: + detailsDict = {} + + contract_db = os.path.join(dbfolder, 'smartContracts', f"{contract[3]}-{contract[4]}.db") + # Make db connection and fetch contract structure + conn = sqlite3.connect(contract_db) + c = conn.cursor() + + # Get details of the type of Smart Contract + c.execute('SELECT attribute,value FROM contractstructure') + result = c.fetchall() + + contractStructure = {} + conditionDict = {} + counter = 0 + for item in result: + 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']=='continuos-event' and contractStructure['subtype']=='tokenswap': + # normal result + swap details + # what is a api detail + c.execute('SELECT * FROM contractparticipants WHERE participantAddress=?',(floAddress,)) + participant_details = c.fetchall() + pdb.set_trace() + + if len(participant_details) > 0: + participationList = [] + + for participation in participant_details: + c.execute("SELECT value FROM contractstructure WHERE attribute='selling_token'") + structure = c.fetchall() + detailsDict['participationAddress'] = floAddress + detailsDict['participationAmount'] = participation[2] + detailsDict['receivedAmount'] = float(participation[3]) + detailsDict['participationToken'] = contractStructure['accepting_token'] + detailsDict['receivedToken'] = contractStructure['selling_token'] + detailsDict['swapPrice_received_to_participation'] = float(participation[7]) + detailsDict['transactionHash'] = participation[4] + detailsDict['blockNumber'] = participation[5] + detailsDict['blockHash'] = participation[6] + participationList.append(detailsDict) + + participationDetailsList.append(participationList) + + elif contractStructure['contractType']=='one-time-event' and 'payeeAddress' in contractStructure.keys(): + # normal results + pass + elif contractStructure['contractType']=='one-time-event' and 'exitconditions' in contractStructure.keys(): + # normal results + winning/losing details + conn = sqlite3.connect(dblocation) + c = conn.cursor() + + detailsDict = {} + detailsDict['contractName'] = row[3] + detailsDict['contractAddress'] = row[4] + detailsDict['tokenAmount'] = row[5] + detailsDict['transactionHash'] = row[6] + + c.execute(f"select status, tokenIdentification, contractType, blockNumber, blockHash, incorporationDate, expiryDate, closeDate from activecontracts where contractName='{detailsDict['contractName']}' and contractAddress='{detailsDict['contractAddress']}'") + temp = c.fetchall() + detailsDict['status'] = temp[0][0] + detailsDict['tokenIdentification'] = temp[0][1] + detailsDict['contractType'] = temp[0][2] + detailsDict['blockNumber'] = temp[0][3] + detailsDict['blockHash'] = temp[0][4] + detailsDict['incorporationDate'] = temp[0][5] + if temp[0][6]: + detailsDict['expiryDate'] = temp[0][6] + if temp[0][7]: + detailsDict['closeDate'] = temp[0][7] + + # check if the contract has been closed + contractDbName = '{}-{}.db'.format(detailsDict['contractName'].strip(), detailsDict['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() + contractStructure = {} + conditionDict = {} + counter = 0 + for item in result: + 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 'exitconditions' in contractStructure: + # contract is of the type external trigger + # check if the contract has been closed + c.execute( + 'select * from contractTransactionHistory where transactionType="trigger"') + trigger = c.fetchall() + + if detailsDict['status'] == 'closed': + c.execute( + f"SELECT userChoice, winningAmount FROM contractparticipants where participantAddress='{floAddress}'") + result = c.fetchall() + conn.close() + detailsDict['userChoice'] = result[0][0] + detailsDict['winningAmount'] = result[0][1] + else: + c.execute( + f"SELECT userChoice FROM contractparticipants where participantAddress='{floAddress}'") + result = c.fetchall() + conn.close() + detailsDict['userChoice'] = result[0][0] + + participationDetailsList.append(detailsDict) + + return jsonify(result='ok', floAddress=floAddress, type='participant', participatedContracts=participationDetailsList) + + else: + return jsonify(result='error', description='Address hasn\'t participated in any other contract') else: return jsonify(result='error', description='System error. System db is missing') @@ -806,14 +951,15 @@ async def getsmartcontracttransactions(): contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') + pdb.set_trace() + if contractName is None: return jsonify(result='error', description='Smart Contract\'s name hasn\'t been passed') if contractAddress is None: return jsonify(result='error', description='Smart Contract\'s address hasn\'t been passed') - contractDbName = '{}-{}.db'.format(contractName.strip(), - contractAddress.strip()) + contractDbName = '{}-{}.db'.format(contractName.strip(), contractAddress.strip()) filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) if os.path.isfile(filelocation): @@ -862,6 +1008,85 @@ async def gettransactiondetails(transactionHash): return jsonify(result='error', description='Transaction doesn\'t exist in database') +@app.route('/api/v2.0/getTransactionDetails/', methods=['GET']) +async def gettransactiondetails1(transactionHash): + transactionJsonData = transactiondetailhelper(transactionHash) + if len(transactionJsonData) != 0: + transactionJson = json.loads(transactionJsonData[0][0]) + parseResult = json.loads(transactionJsonData[0][1]) + operation = transactionJsonData[0][2] + db_reference = transactionJsonData[0][3] + + operationDetails = {} + + if operation == 'smartContractDeposit': + # open the db reference and check if there is a deposit return + conn = sqlite3.connect(f"{dbfolder}/smartContracts/{db_reference}.db") + c = conn.cursor() + c.execute("SELECT depositAmount, blockNumber FROM contractdeposits WHERE status='deposit-return' AND transactionHash=?",(transactionJson['txid'],)) + returned_deposit_tx = c.fetchall() + if len(returned_deposit_tx) == 1: + operationDetails['returned_depositAmount'] = returned_deposit_tx[0][0] + operationDetails['returned_blockNumber'] = returned_deposit_tx[0][1] + c.execute("SELECT depositAmount, blockNumber FROM contractdeposits WHERE status='deposit-honor' AND transactionHash=?",(transactionJson['txid'],)) + deposit_honors = c.fetchall() + operationDetails['depositHonors'] = {} + operationDetails['depositHonors']['list'] = [] + operationDetails['depositHonors']['count'] = len(deposit_honors) + for deposit_honor in deposit_honors: + operationDetails['depositHonors']['list'].append({'honor_amount':deposit_honor[0],'blockNumber':deposit_honor[1]}) + + c.execute("SELECT depositBalance FROM contractdeposits WHERE id=(SELECT max(id) FROM contractdeposits WHERE transactionHash=?)",(transactionJson['txid'],)) + depositBalance = c.fetchall() + operationDetails['depositBalance'] = depositBalance[0][0] + operationDetails['consumedAmount'] = parseResult['depositAmount'] - operationDetails['depositBalance'] + + elif operation == 'tokenswap-participation': + conn = sqlite3.connect(f"{dbfolder}/smartContracts/{db_reference}.db") + c = conn.cursor() + c.execute('SELECT tokenAmount, winningAmount, userChoice FROM contractparticipants WHERE transactionHash=?',(transactionJson['txid'],)) + swap_amounts = c.fetchall() + c.execute("SELECT value FROM contractstructure WHERE attribute='selling_token'") + structure = c.fetchall() + operationDetails['participationAmount'] = swap_amounts[0][0] + operationDetails['receivedAmount'] = swap_amounts[0][1] + operationDetails['participationToken'] = parseResult['tokenIdentification'] + operationDetails['receivedToken'] = structure[0][0] + operationDetails['swapPrice_received_to_participation'] = float(swap_amounts[0][2]) + + elif operation == 'smartContractPays': + pdb.set_trace() + # Find what happened because of the trigger + # Find who + conn = sqlite3.connect(f"{dbfolder}/smartContracts/{db_reference}.db") + c = conn.cursor() + c.execute('SELECT participantAddress, tokenAmount, userChoice, winningAmount FROM contractparticipants WHERE winningAmount IS NOT NULL') + winner_participants = c.fetchall() + if len(winner_participants) != 0: + operationDetails['total_winners'] = len(winner_participants) + operationDetails['winning_choice'] = winner_participants[0][2] + operationDetails['winner_list'] = [] + for participant in winner_participants: + winner_details = {} + winner_details['participantAddress'] = participant[0] + winner_details['participationAmount'] = participant[1] + winner_details['winningAmount'] = participant[3] + operationDetails['winner_list'].append(winner_details) + + elif operation == 'ote-externaltrigger-participation': + # Find if this guy has won + conn = sqlite3.connect(f"{dbfolder}/smartContracts/{db_reference}.db") + c = conn.cursor() + c.execute('SELECT winningAmount FROM contractparticipants WHERE transactionHash=?',(transactionHash,)) + winningAmount = c.fetchall() + if winningAmount[0][0] is not None: + operationDetails['winningAmount'] = winningAmount[0][0] + + return jsonify(parsedFloData=parseResult, transactionDetails=transactionJson, transactionHash=transactionHash, operation=operation, operationDetails=operationDetails, result='ok') + else: + return jsonify(result='error', description='Transaction doesn\'t exist in database') + + @app.route('/api/v1.0/getLatestTransactionDetails', methods=['GET']) async def getLatestTransactionDetails(): @@ -885,8 +1110,7 @@ async def getLatestTransactionDetails(): tx_parsed_details['transactionDetails'] = json.loads(item[3]) tx_parsed_details['parsedFloData'] = json.loads(item[5]) tx_parsed_details['parsedFloData']['transactionType'] = item[4] - tx_parsed_details['transactionDetails']['blockheight'] = int( - item[2]) + tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) tempdict[json.loads(item[3])['txid']] = tx_parsed_details else: c.execute('''SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT 100) ORDER BY id ASC;''') @@ -899,8 +1123,7 @@ async def getLatestTransactionDetails(): tx_parsed_details['transactionDetails'] = json.loads(item[3]) tx_parsed_details['parsedFloData'] = json.loads(item[5]) tx_parsed_details['parsedFloData']['transactionType'] = item[4] - tx_parsed_details['transactionDetails']['blockheight'] = int( - item[2]) + tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) tempdict[json.loads(item[3])['txid']] = tx_parsed_details return jsonify(result='ok', latestTransactions=tempdict) @@ -1076,7 +1299,8 @@ class ServerSentEvent: return message.encode('utf-8') -"""@app.route('/', methods=['GET']) +""" +@app.route('/', methods=['GET']) async def index(): return await render_template('index.html') @@ -1086,7 +1310,8 @@ async def broadcast(): data = await request.get_json() for queue in app.clients: await queue.put(data['message']) - return jsonify(True) """ + return jsonify(True) +""" @app.route('/sse') @@ -1117,7 +1342,6 @@ async def sse(): @app.route('/api/v1.0/getPrices', methods=['GET']) async def getPriceData(): - # read system.db for price data conn = sqlite3.connect('system.db') c = conn.cursor() @@ -1133,7 +1357,6 @@ async def getPriceData(): ''' Stuff required for getPrices endpoint ''' - def updatePrices(): prices = {} # USD -> INR @@ -1151,7 +1374,7 @@ def updatePrices(): # FLO->USD | FLO->INR response = requests.get(f"https://api.coinlore.net/api/ticker/?id=67") price = response.json() - prices["FLOUSD"] = price[0]['price_usd'] + prices["FLOUSD"] = float(price[0]['price_usd']) prices["FLOINR"] = float(prices["FLOUSD"]) * float(prices['USDINR']) # 3. update latest price data @@ -1192,4 +1415,4 @@ scheduler.start() atexit.register(lambda: scheduler.shutdown()) if __name__ == "__main__": - app.run(debug=True, host='0.0.0.0', port=5009) + app.run(debug=True, host='0.0.0.0', port=5009) \ No newline at end of file From 5f7e49704322a2acd691d30ad9c97dc93113c299 Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Tue, 5 Apr 2022 04:53:03 +0000 Subject: [PATCH 02/46] getParticipantDetails : Added support for one-time-event internal trigger contract --- ranchimallflo_api.py | 80 +++++++++++++++++++++++++++++++++----------- 1 file changed, 60 insertions(+), 20 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 6fbb6e3..d692993 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -816,7 +816,6 @@ async def getParticipantDetails(): participationDetailsList = [] for contract in participant_address_contracts: detailsDict = {} - contract_db = os.path.join(dbfolder, 'smartContracts', f"{contract[3]}-{contract[4]}.db") # Make db connection and fetch contract structure conn = sqlite3.connect(contract_db) @@ -848,7 +847,6 @@ async def getParticipantDetails(): if len(participant_details) > 0: participationList = [] - for participation in participant_details: c.execute("SELECT value FROM contractstructure WHERE attribute='selling_token'") structure = c.fetchall() @@ -867,17 +865,66 @@ async def getParticipantDetails(): elif contractStructure['contractType']=='one-time-event' and 'payeeAddress' in contractStructure.keys(): # normal results - pass + conn = sqlite3.connect(dblocation) + c = conn.cursor() + detailsDict = {} + detailsDict['contractName'] = contract[3] + detailsDict['contractAddress'] = contract[4] + detailsDict['tokenAmount'] = contract[5] + detailsDict['transactionHash'] = contract[6] + + c.execute(f"select status, tokenIdentification, contractType, blockNumber, blockHash, incorporationDate, expiryDate, closeDate from activecontracts where contractName='{detailsDict['contractName']}' and contractAddress='{detailsDict['contractAddress']}'") + temp = c.fetchall() + detailsDict['status'] = temp[0][0] + detailsDict['tokenIdentification'] = temp[0][1] + detailsDict['contractType'] = temp[0][2] + detailsDict['blockNumber'] = temp[0][3] + detailsDict['blockHash'] = temp[0][4] + detailsDict['incorporationDate'] = temp[0][5] + if temp[0][6]: + detailsDict['expiryDate'] = temp[0][6] + if temp[0][7]: + detailsDict['closeDate'] = temp[0][7] + + # check if the contract has been closed + contractDbName = '{}-{}.db'.format(detailsDict['contractName'].strip(), detailsDict['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() + contractStructure = {} + conditionDict = {} + counter = 0 + for item in result: + 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 'payeeAddress' in contractStructure: + # contract is of the type external trigger + # check if the contract has been closed + c.execute(f"SELECT tokenAmount FROM contractparticipants where participantAddress='{floAddress}'") + result = c.fetchall() + conn.close() + detailsDict['tokenAmount'] = result[0][0] + elif contractStructure['contractType']=='one-time-event' and 'exitconditions' in contractStructure.keys(): # normal results + winning/losing details conn = sqlite3.connect(dblocation) c = conn.cursor() - detailsDict = {} - detailsDict['contractName'] = row[3] - detailsDict['contractAddress'] = row[4] - detailsDict['tokenAmount'] = row[5] - detailsDict['transactionHash'] = row[6] + detailsDict['contractName'] = contract[3] + detailsDict['contractAddress'] = contract[4] + detailsDict['tokenAmount'] = contract[5] + detailsDict['transactionHash'] = contract[6] c.execute(f"select status, tokenIdentification, contractType, blockNumber, blockHash, incorporationDate, expiryDate, closeDate from activecontracts where contractName='{detailsDict['contractName']}' and contractAddress='{detailsDict['contractAddress']}'") temp = c.fetchall() @@ -899,8 +946,7 @@ async def getParticipantDetails(): # Make db connection and fetch data conn = sqlite3.connect(filelocation) c = conn.cursor() - c.execute( - 'SELECT attribute,value FROM contractstructure') + c.execute('SELECT attribute,value FROM contractstructure') result = c.fetchall() contractStructure = {} conditionDict = {} @@ -918,20 +964,17 @@ async def getParticipantDetails(): if 'exitconditions' in contractStructure: # contract is of the type external trigger # check if the contract has been closed - c.execute( - 'select * from contractTransactionHistory where transactionType="trigger"') + c.execute('select * from contractTransactionHistory where transactionType="trigger"') trigger = c.fetchall() if detailsDict['status'] == 'closed': - c.execute( - f"SELECT userChoice, winningAmount FROM contractparticipants where participantAddress='{floAddress}'") + c.execute(f"SELECT userChoice, winningAmount FROM contractparticipants where participantAddress='{floAddress}'") result = c.fetchall() conn.close() detailsDict['userChoice'] = result[0][0] detailsDict['winningAmount'] = result[0][1] else: - c.execute( - f"SELECT userChoice FROM contractparticipants where participantAddress='{floAddress}'") + c.execute(f"SELECT userChoice FROM contractparticipants where participantAddress='{floAddress}'") result = c.fetchall() conn.close() detailsDict['userChoice'] = result[0][0] @@ -951,8 +994,6 @@ async def getsmartcontracttransactions(): contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') - pdb.set_trace() - if contractName is None: return jsonify(result='error', description='Smart Contract\'s name hasn\'t been passed') @@ -1393,8 +1434,7 @@ if not os.path.isfile(f"system.db"): # create an empty db conn = sqlite3.connect('system.db') c = conn.cursor() - c.execute('''CREATE TABLE ratepairs - (id integer primary key, ratepair text, price real)''') + c.execute('''CREATE TABLE ratepairs (id integer primary key, ratepair text, price real)''') c.execute("INSERT INTO ratepairs(ratepair, price) VALUES ('BTCBTC', 1)") c.execute("INSERT INTO ratepairs(ratepair, price) VALUES ('BTCUSD', -1)") c.execute("INSERT INTO ratepairs(ratepair, price) VALUES ('BTCINR', -1)") From 0eabc5bcde1f047b70ea546886d6a5eede0cd02e Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Wed, 18 Jan 2023 11:34:26 +0000 Subject: [PATCH 03/46] Rearranged the code & fixed a bug in participant details API --- ranchimallflo_api.py | 430 ++++++++++++++++++++++++++----------------- 1 file changed, 259 insertions(+), 171 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 765cedb..b3f8d27 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -22,6 +22,7 @@ import pathlib import io import zipfile import tarfile +import pdb app = Quart(__name__) @@ -48,18 +49,14 @@ def retryRequest(tempserverlist, apicall): print("None of the APIs are responding for the call {}".format(apicall)) sys.exit(0) - def multiRequest(apicall, net): - testserverlist = ['http://0.0.0.0:9000/', 'https://testnet.flocha.in/', - 'https://testnet-flosight.duckdns.org/'] - mainserverlist = ['http://0.0.0.0:9001/', 'https://livenet.flocha.in/', - 'https://testnet-flosight.duckdns.org/'] + testserverlist = ['http://0.0.0.0:9000/', 'https://testnet.flocha.in/', 'https://testnet-flosight.duckdns.org/'] + mainserverlist = ['http://0.0.0.0:9001/', 'https://livenet.flocha.in/', 'https://testnet-flosight.duckdns.org/'] if net == 'mainnet': return retryRequest(mainserverlist, apicall) elif net == 'testnet': return retryRequest(testserverlist, apicall) - def blockdetailhelper(blockdetail): if blockdetail.isdigit(): blockHash = None @@ -82,7 +79,6 @@ def blockdetailhelper(blockdetail): blockJson = c.fetchall() return blockJson - def transactiondetailhelper(transactionHash): # open the latest block database conn = sqlite3.connect(os.path.join(dbfolder, 'latestCache.db')) @@ -93,7 +89,6 @@ def transactiondetailhelper(transactionHash): return transactionJsonData - def smartContractInfo_output(contractName, contractAddress, contractType, subtype): if contractType == 'continuos-event' and contractType == 'tokenswap': pass @@ -102,6 +97,118 @@ def smartContractInfo_output(contractName, contractAddress, contractType, subtyp elif contractType == 'one-time-event' and contractType == 'timetrigger': pass +def return_smart_contracts(connection, contractName=None, contractAddress=None): + # find all the contracts details + if contractName and contractAddress: + connection.execute("SELECT * FROM activecontracts WHERE id IN (SELECT max(id) FROM activecontracts GROUP BY contractName, contractAddress) AND contractName=? AND contractAddress=?", (contractName, contractAddress)) + elif contractName and not contractAddress: + connection.execute("SELECT * FROM activecontracts WHERE id IN (SELECT max(id) FROM activecontracts GROUP BY contractName, contractAddress) AND contractName=?", (contractName,)) + elif not contractName and contractAddress: + connection.execute("SELECT * FROM activecontracts WHERE id IN (SELECT max(id) FROM activecontracts GROUP BY contractName, contractAddress) AND contractAddress=?", (contractAddress,)) + else: + connection.execute("SELECT * FROM activecontracts WHERE id IN (SELECT max(id) FROM activecontracts GROUP BY contractName, contractAddress)") + + smart_contracts = connection.fetchall() + return smart_contracts + +def create_database_connection(type, parameters=None): + if type == 'token': + filelocation = os.path.join(dbfolder, 'tokens', parameters['token_name']) + elif type == 'smart_contract': + contractDbName = '{}-{}.db'.format(parameters['contract_name'].strip(), parameters['contract_address'].strip()) + filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) + elif type == 'system_dbs': + filelocation = os.path.join(dbfolder, 'system.db') + elif type == 'latest_cache': + filelocation = os.path.join(dbfolder, 'latestCache.db') + + conn = sqlite3.connect(filelocation) + c = conn.cursor() + return [conn, c] + +def fetchContractStructure(contractName, contractAddress): + # Make connection to contract database + contractDbName = '{}-{}.db'.format(contractName.strip(),contractAddress.strip()) + filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) + if os.path.isfile(filelocation): + # fetch from contractStructure + conn = sqlite3.connect(filelocation) + c = conn.cursor() + c.execute('SELECT attribute,value FROM contractstructure') + result = c.fetchall() + + contractStructure = {} + conditionDict = {} + counter = 0 + for item in result: + 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, c + conn.close() + return contractStructure + else: + return 0 + +def fetchContractStatus(contractName, contractAddress): + conn, c = create_database_connection('system_dbs') + # select status from the last instance of activecontracts where match contractName and contractAddress + c.execute(f'SELECT status FROM activecontracts WHERE contractName="{contractName}" AND contractAddress="{contractAddress}" ORDER BY id DESC LIMIT 1') + status = c.fetchall() + if len(status)==0: + return None + else: + return status[0][0] + + +@app.route('/') +async def welcome_msg(): + return jsonify('Welcome to RanchiMall FLO Api v2') + +@app.route('/api/v1.0/getSystemData', methods=['GET']) +async def systemData(): + # query for the number of flo addresses in tokenAddress mapping + conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) + c = conn.cursor() + tokenAddressCount = c.execute('select count(distinct tokenAddress) from tokenAddressMapping').fetchall()[0][0] + tokenCount = c.execute('select count(distinct token) from tokenAddressMapping').fetchall()[0][0] + contractCount = c.execute('select count(distinct contractName) from contractAddressMapping').fetchall()[0][0] + lastscannedblock = int(c.execute("select value from systemData where attribute=='lastblockscanned'").fetchall()[0][0]) + conn.close() + + # query for total number of validated blocks + conn = sqlite3.connect(os.path.join(dbfolder, 'latestCache.db')) + c = conn.cursor() + validatedBlockCount = c.execute('select count(distinct blockNumber) from latestBlocks').fetchall()[0][0] + validatedTransactionCount = c.execute('select count(distinct transactionHash) from latestTransactions').fetchall()[0][0] + conn.close() + + return jsonify(systemAddressCount=tokenAddressCount, systemBlockCount=validatedBlockCount, systemTransactionCount=validatedTransactionCount, systemSmartContractCount=contractCount, systemTokenCount=tokenCount, lastscannedblock=lastscannedblock, result='ok') + +@app.route('/api/v2.0/info', methods=['GET']) +async def info(): + # query for the number of flo addresses in tokenAddress mapping + conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) + c = conn.cursor() + tokenAddressCount = c.execute('select count(distinct tokenAddress) from tokenAddressMapping').fetchall()[0][0] + tokenCount = c.execute('select count(distinct token) from tokenAddressMapping').fetchall()[0][0] + contractCount = c.execute('select count(distinct contractName) from contractAddressMapping').fetchall()[0][0] + lastscannedblock = int(c.execute("select value from systemData where attribute=='lastblockscanned'").fetchall()[0][0]) + conn.close() + + # query for total number of validated blocks + conn = sqlite3.connect(os.path.join(dbfolder, 'latestCache.db')) + c = conn.cursor() + validatedBlockCount = c.execute('select count(distinct blockNumber) from latestBlocks').fetchall()[0][0] + validatedTransactionCount = c.execute('select count(distinct transactionHash) from latestTransactions').fetchall()[0][0] + conn.close() + + return jsonify(systemAddressCount=tokenAddressCount, systemBlockCount=validatedBlockCount, systemTransactionCount=validatedTransactionCount, systemSmartContractCount=contractCount, systemTokenCount=tokenCount, lastscannedblock=lastscannedblock, result='ok') + # FLO TOKEN APIs @app.route('/api/v1.0/broadcastTx/') @@ -139,8 +246,7 @@ async def getTokenInfo(): numberOf_distinctAddresses = c.fetchall()[0][0] c.execute('select max(id) from transactionHistory') numberOf_transactions = c.fetchall()[0][0] - c.execute( - 'select contractName, contractAddress, blockNumber, blockHash, transactionHash from tokenContractAssociation') + c.execute('select contractName, contractAddress, blockNumber, blockHash, transactionHash from tokenContractAssociation') associatedContracts = c.fetchall() conn.close() @@ -180,29 +286,19 @@ async def getTokenTransactions(): if senderFloAddress and not destFloAddress: if limit is None: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC LIMIT 100'.format( - senderFloAddress)) + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(senderFloAddress)) else: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC LIMIT {}'.format( - senderFloAddress, limit)) + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(senderFloAddress, limit)) elif not senderFloAddress and destFloAddress: if limit is None: - c.execute( - 'SELECT jsonData, parsedFloData FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format( - destFloAddress)) + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(destFloAddress)) else: - c.execute( - 'SELECT jsonData, parsedFloData FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format( - destFloAddress, limit)) + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(destFloAddress, limit)) elif senderFloAddress and destFloAddress: if limit is None: - c.execute( - 'SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format( - senderFloAddress, destFloAddress)) + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(senderFloAddress, destFloAddress)) else: - c.execute( - 'SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format( - senderFloAddress, destFloAddress, limit)) + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(senderFloAddress, destFloAddress, limit)) else: if limit is None: @@ -247,7 +343,6 @@ async def getTokenBalances(): # FLO address APIs - @app.route('/api/v1.0/getFloAddressInfo', methods=['GET']) async def getFloAddressInfo(): floAddress = request.args.get('floAddress') @@ -375,8 +470,7 @@ async def getFloAddressTransactions(): if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() - c.execute( - 'select token from tokenAddressMapping where tokenAddress="{}"'.format(floAddress)) + c.execute('select token from tokenAddressMapping where tokenAddress="{}"'.format(floAddress)) tokenNames = c.fetchall() else: dblocation = dbfolder + '/tokens/' + str(token) + '.db' @@ -420,7 +514,6 @@ async def getFloAddressTransactions(): # SMART CONTRACT APIs - @app.route('/api/v1.0/getSmartContractList', methods=['GET']) async def getContractList(): contractName = request.args.get('contractName') @@ -432,8 +525,7 @@ async def getContractList(): contractList = [] if contractName and contractAddress: - c.execute('select * from activecontracts where contractName="{}" and contractAddress="{}"'.format( - contractName, contractAddress)) + c.execute('select * from activecontracts where contractName="{}" and contractAddress="{}"'.format(contractName, contractAddress)) allcontractsDetailList = c.fetchall() for idx, contract in enumerate(allcontractsDetailList): contractDict = {} @@ -453,8 +545,7 @@ async def getContractList(): contractList.append(contractDict) elif contractName and not contractAddress: - c.execute( - 'select * from activecontracts where contractName="{}"'.format(contractName)) + c.execute('select * from activecontracts where contractName="{}"'.format(contractName)) allcontractsDetailList = c.fetchall() for idx, contract in enumerate(allcontractsDetailList): contractDict = {} @@ -474,8 +565,7 @@ async def getContractList(): contractList.append(contractDict) elif not contractName and contractAddress: - c.execute( - 'select * from activecontracts where contractAddress="{}"'.format(contractAddress)) + c.execute('select * from activecontracts where contractAddress="{}"'.format(contractAddress)) allcontractsDetailList = c.fetchall() for idx, contract in enumerate(allcontractsDetailList): contractDict = {} @@ -513,7 +603,33 @@ async def getContractList(): contractDict['closeDate'] = contract[10] contractList.append(contractDict) + + return jsonify(smartContracts=contractList, result='ok') + +@app.route('/api/v2.0/smartContractList', methods=['GET']) +async def getContractList_v2(): + contractName = request.args.get('contractName') + contractAddress = request.args.get('contractAddress') + # todo - Add validation for contractAddress and contractName to prevent SQL injection attacks + contractList = [] + conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) + c = conn.cursor() + smart_contracts = return_smart_contracts(c, contractName, contractAddress) + for idx, contract in enumerate(smart_contracts): + contractDict = {} + contractDict['contractName'] = contract[1] + contractDict['contractAddress'] = contract[2] + contractDict['status'] = contract[3] + contractDict['tokenIdentification'] = contract[4] + contractDict['contractType'] = contract[5] + contractDict['transactionHash'] = contract[6] + contractDict['blockNumber'] = contract[7] + contractDict['incorporationDate'] = contract[8] + contractDict['expiryDate'] = contract[9] + contractDict['closeDate'] = contract[10] + contractList.append(contractDict) + conn.close() return jsonify(smartContracts=contractList, result='ok') @@ -618,62 +734,35 @@ async def getContractInfo(): return jsonify(result='error', details='Smart Contract with the given name doesn\'t exist') -@app.route('/api/v2.0/getSmartContractInfo', methods=['GET']) -async def getContractInfo2(): +@app.route('/api/v2.0/smartContractInfo', methods=['GET']) +async def getContractInfo_v2(): contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') - if contractName is None: return jsonify(result='error', description='Smart Contract\'s name hasn\'t been passed') - if contractAddress is None: return jsonify(result='error', description='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() - - contractStructure = {} - conditionDict = {} - counter = 0 - for item in result: - 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 - + contractName = contractName.strip() + contractAddress = contractAddress.strip() + contractStructure = fetchContractStructure(contractName, contractAddress) + if contractStructure: returnval = contractStructure - pdb.set_trace() # Categorize into what type of contract it is right now if contractStructure['contractType'] == 'continuos-event' and contractStructure['subtype'] == 'tokenswap': + conn, c = create_database_connection('smart_contract', {'contract_name': contractName, 'contract_address': contractAddress}) c.execute('select count(participantAddress), sum(tokenAmount), sum(winningAmount) from contractparticipants') participation_details = c.fetchall() returnval['numberOfParticipants'] = participation_details[0][0] returnval['totalParticipationAmount'] = participation_details[0][1] returnval['totalHonorAmount'] = participation_details[0][2] - c.execute('SELECT COUNT(DISTINCT transactionHash) FROM contractdeposits') returnval['numberOfDeposits'] = c.fetchall()[0][0] - c.execute('SELECT COUNT(DISTINCT transactionHash) FROM contractdeposits') - returnval['numberOfDeposits'] = c.fetchall()[0][0] - conn.close() - + elif contractStructure['contractType'] == 'one-time-event' and 'exitconditions' in contractStructure.keys(): returnval['userChoice'] = contractStructure['exitconditions'] returnval.pop('exitconditions') - - conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) - c = conn.cursor() + conn, c = create_database_connection('system_dbs') c.execute('select status, incorporationDate, expiryDate, closeDate from activecontracts where contractName=="{}" and contractAddress=="{}"'.format(contractName.strip(), contractAddress.strip())) results = c.fetchall() @@ -705,28 +794,14 @@ async def getcontractparticipants(): if contractAddress is None: return jsonify(result='error', description='Smart Contract\'s address hasn\'t been passed') - contractDbName = '{}-{}.db'.format(contractName.strip(), contractAddress.strip()) - filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) + contractName = contractName.strip() + contractAddress = contractAddress.strip() + filelocation = os.path.join(dbfolder, 'smartContracts', '{}-{}.db'.format(contractName, contractAddress)) 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() - - contractStructure = {} - conditionDict = {} - counter = 0 - for item in result: - 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 + contractStructure = fetchContractStructure(contractName, contractAddress) + conn, c = create_database_connection('smart_contract', {'contract_name': contractName, 'contract_address': contractAddress}) if 'exitconditions' in contractStructure: # contract is of the type external trigger @@ -769,7 +844,74 @@ async def getcontractparticipants(): 'transactionHash': row[4]} elif contractStructure['contractType'] == 'continuos-event' and contractStructure['subtype'] == 'tokenswap': - pdb.set_trace() + c.execute('SELECT * FROM contractparticipants') + contract_participants = c.fetchall() + returnval = {} + for row in contract_participants: + returnval[row[1]] = { + 'participantFloAddress': row[1], + 'participationAmount': row[2], + 'swapPrice': float(row[3]), + 'transactionHash': row[4], + 'blockNumber': row[5], + 'blockHash': row[6], + 'swapAmount': row[7] + } + conn.close() + + return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, participantInfo=returnval) + else: + return jsonify(result='error', description='Smart Contract with the given name doesn\'t exist') + + +@app.route('/api/v2.0/smartContractParticipants', methods=['GET']) +async def getcontractparticipants_v2(): + contractName = request.args.get('contractName') + contractAddress = request.args.get('contractAddress') + + if contractName is None: + return jsonify(result='error', description='Smart Contract\'s name hasn\'t been passed') + if contractAddress is None: + return jsonify(result='error', description='Smart Contract\'s address hasn\'t been passed') + + contractName = contractName.strip() + contractAddress = contractAddress.strip() + filelocation = os.path.join(dbfolder, 'smartContracts', '{}-{}.db'.format(contractName, contractAddress)) + + if os.path.isfile(filelocation): + # Make db connection and fetch data + contractStructure = fetchContractStructure(contractName, contractAddress) + contractStatus = fetchContractStatus(contractName, contractAddress) + conn, c = create_database_connection('smart_contract', {'contract_name': contractName, 'contract_address': contractAddress}) + if 'exitconditions' in contractStructure: + # contract is of the type external trigger + # check if the contract has been closed + if contractStatus == 'closed': + token = contractStructure['tokenIdentification'] + c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash, winningAmount FROM contractparticipants') + result = c.fetchall() + returnval = {} + for row in result: + returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4], 'winningAmount': row[5], 'tokenIdentification': token} + + else: + c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash FROM contractparticipants') + result = c.fetchall() + conn.close() + returnval = {} + for row in result: + returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4]} + + elif 'payeeAddress' in contractStructure: + # contract is of the type internal trigger + c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash FROM contractparticipants') + result = c.fetchall() + conn.close() + returnval = {} + for row in result: + returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'transactionHash': row[4]} + + elif contractStructure['contractType'] == 'continuos-event' and contractStructure['subtype'] == 'tokenswap': c.execute('SELECT * FROM contractparticipants') contract_participants = c.fetchall() returnval = {} @@ -846,7 +988,6 @@ async def getParticipantDetails(): # what is a api detail c.execute('SELECT * FROM contractparticipants WHERE participantAddress=?',(floAddress,)) participant_details = c.fetchall() - pdb.set_trace() if len(participant_details) > 0: participationList = [] @@ -1099,7 +1240,6 @@ async def gettransactiondetails1(transactionHash): operationDetails['swapPrice_received_to_participation'] = float(swap_amounts[0][2]) elif operation == 'smartContractPays': - pdb.set_trace() # Find what happened because of the trigger # Find who conn = sqlite3.connect(f"{dbfolder}/smartContracts/{db_reference}.db") @@ -1185,12 +1325,10 @@ async def getLatestBlockDetails(): return 'Latest transactions db doesn\'t exist. This is unusual, please report on https://github.com/ranchimall/ranchimallflo-api' if limit is None: - c.execute( - '''SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT 4) ORDER BY id ASC;''') + c.execute('''SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT 4) ORDER BY id ASC;''') else: int(limit) - c.execute( - 'SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT {}) ORDER BY id ASC;'.format(limit)) + c.execute('SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT {}) ORDER BY id ASC;'.format(limit)) latestBlocks = c.fetchall() c.close() tempdict = {} @@ -1221,7 +1359,6 @@ async def getblocktransactions(blockdetail): @app.route('/api/v1.0/categoriseString/') async def categoriseString(urlstring): - # check if the hash is of a transaction response = requests.get('{}tx/{}'.format(apiUrl, urlstring)) if response.status_code == 200: @@ -1289,46 +1426,6 @@ async def getTokenSmartContractList(): return jsonify(tokens=filelist, smartContracts=contractList, result='ok') -@app.route('/api/v1.0/getSystemData', methods=['GET']) -async def systemData(): - - # query for the number of flo addresses in tokenAddress mapping - conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) - c = conn.cursor() - tokenAddressCount = c.execute( - 'select count(distinct tokenAddress) from tokenAddressMapping').fetchall()[0][0] - tokenCount = c.execute( - 'select count(distinct token) from tokenAddressMapping').fetchall()[0][0] - contractCount = c.execute( - 'select count(distinct contractName) from contractAddressMapping').fetchall()[0][0] - lastscannedblock = int(c.execute("select value from systemData where attribute=='lastblockscanned'").fetchall()[0][0]) - conn.close() - - # query for total number of validated blocks - conn = sqlite3.connect(os.path.join(dbfolder, 'latestCache.db')) - c = conn.cursor() - validatedBlockCount = c.execute( - 'select count(distinct blockNumber) from latestBlocks').fetchall()[0][0] - validatedTransactionCount = c.execute( - 'select count(distinct transactionHash) from latestTransactions').fetchall()[0][0] - conn.close() - - return jsonify(systemAddressCount=tokenAddressCount, systemBlockCount=validatedBlockCount, systemTransactionCount=validatedTransactionCount, systemSmartContractCount=contractCount, systemTokenCount=tokenCount, lastscannedblock=lastscannedblock, result='ok') - - -'''@app.route('/api/v1.0/floscout-bootstrap') -def request_zip(): - directory = pathlib.Path("") - data = io.BytesIO() - with zipfile.ZipFile(data, mode="w") as archive: - for file_path in directory.rglob("*"): - archive.write(file_path, arcname=file_path.name) - data.seek(0) - return send_file( data, - mimetype='application/zip', - as_attachment=True, - attachment_filename='data.zip')''' - class ServerSentEvent: def __init__( @@ -1356,21 +1453,6 @@ class ServerSentEvent: 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(): - data = await request.get_json() - for queue in app.clients: - await queue.put(data['message']) - return jsonify(True) -""" - - @app.route('/sse') async def sse(): queue = asyncio.Queue() @@ -1397,22 +1479,6 @@ async def sse(): return response -@app.route('/api/v1.0/getPrices', methods=['GET']) -async def getPriceData(): - # read system.db for price data - conn = sqlite3.connect('system.db') - c = conn.cursor() - ratepairs = c.execute('select ratepair, price from ratepairs') - ratepairs = ratepairs.fetchall() - prices = {} - - for ratepair in ratepairs: - ratepair = list(ratepair) - prices[ratepair[0]] = ratepair[1] - - return jsonify(prices=prices, result='ok') - - ''' Stuff required for getPrices endpoint ''' def updatePrices(): prices = {} @@ -1445,7 +1511,29 @@ def updatePrices(): c.execute(f"UPDATE ratepairs SET price={pair[1]} WHERE ratepair='{pair[0]}'") conn.commit() +@app.route('/api/v1.0/getPrices', methods=['GET']) +async def getPriceData(): + # read system.db for price data + conn = sqlite3.connect('system.db') + c = conn.cursor() + ratepairs = c.execute('select ratepair, price from ratepairs') + ratepairs = ratepairs.fetchall() + prices = {} + + for ratepair in ratepairs: + ratepair = list(ratepair) + prices[ratepair[0]] = ratepair[1] + + return jsonify(prices=prices, result='ok') + + +@app.route('/api/test') +async def test_msg(): + return jsonify('Welcome to Ranchi Mall API') + + # if system.db isn't present, initialize it + if not os.path.isfile(f"system.db"): # create an empty db conn = sqlite3.connect('system.db') @@ -1471,4 +1559,4 @@ scheduler.start() atexit.register(lambda: scheduler.shutdown()) if __name__ == "__main__": - app.run(debug=False, host='0.0.0.0', port=5009) + app.run(debug=True, host='0.0.0.0', port=5009) \ No newline at end of file From 188b64f34723b44a06ad8bf04a8f1c383db0276b Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Wed, 18 Jan 2023 16:01:52 +0000 Subject: [PATCH 04/46] Named the new api version as 'v2' & added debug_status of the API as part of the config file --- config-example.py | 1 + ranchimallflo_api.py | 733 ++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 723 insertions(+), 11 deletions(-) diff --git a/config-example.py b/config-example.py index cdbc039..66b24e0 100644 --- a/config-example.py +++ b/config-example.py @@ -1,4 +1,5 @@ dbfolder = '' +debug_status = False sse_pubKey = '' apiUrl = 'https://flosight.duckdns.org/api/' diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index b3f8d27..f233de5 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -189,7 +189,7 @@ async def systemData(): return jsonify(systemAddressCount=tokenAddressCount, systemBlockCount=validatedBlockCount, systemTransactionCount=validatedTransactionCount, systemSmartContractCount=contractCount, systemTokenCount=tokenCount, lastscannedblock=lastscannedblock, result='ok') -@app.route('/api/v2.0/info', methods=['GET']) +@app.route('/api/v2/info', methods=['GET']) async def info(): # query for the number of flo addresses in tokenAddress mapping conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) @@ -217,6 +217,12 @@ async def broadcastTx(raw_transaction_hash): return jsonify(args=p1.args,returncode=p1.returncode,stdout=p1.stdout.decode(),stderr=p1.stderr.decode()) +@app.route('/api/v2/broadcastTx/') +async def broadcastTx_v2(raw_transaction_hash): + p1 = subprocess.run(['flo-cli',f"-datadir={FLO_DATA_DIR}",'sendrawtransaction',raw_transaction_hash], capture_output=True) + return jsonify(args=p1.args,returncode=p1.returncode,stdout=p1.stdout.decode(),stderr=p1.stderr.decode()) + + @app.route('/api/v1.0/getTokenList', methods=['GET']) async def getTokenList(): filelist = [] @@ -225,7 +231,17 @@ async def getTokenList(): filelist.append(item[:-3]) return jsonify(tokens=filelist, result='ok') - + + +@app.route('/api/v2/tokenList', methods=['GET']) +async def tokenList(): + 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(): @@ -266,6 +282,45 @@ async def getTokenInfo(): activeAddress_no=numberOf_distinctAddresses, totalTransactions=numberOf_transactions, associatedSmartContracts=associatedContractList) +@app.route('/api/v2/tokenInfo', methods=['GET']) +async def tokenInfo(): + token = request.args.get('token') + + if token is None: + return jsonify(result='error', description='token name hasnt been passed') + + dblocation = dbfolder + '/tokens/' + str(token) + '.db' + if os.path.exists(dblocation): + conn = sqlite3.connect(dblocation) + c = conn.cursor() + else: + return jsonify(result='error', description='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] + c.execute('select max(id) from transactionHistory') + numberOf_transactions = c.fetchall()[0][0] + c.execute('select contractName, contractAddress, blockNumber, blockHash, transactionHash from tokenContractAssociation') + associatedContracts = c.fetchall() + conn.close() + + associatedContractList = [] + for item in associatedContracts: + tempdict = {} + item = list(item) + tempdict['contractName'] = item[0] + tempdict['contractAddress'] = item[1] + tempdict['blockNumber'] = item[2] + tempdict['blockHash'] = item[3] + tempdict['transactionHash'] = item[4] + associatedContractList.append(tempdict) + + return jsonify(result='ok', token=token, incorporationAddress=incorporationRow[1], tokenSupply=incorporationRow[3], + time=incorporationRow[6], blockchainReference=incorporationRow[7], + activeAddress_no=numberOf_distinctAddresses, totalTransactions=numberOf_transactions, associatedSmartContracts=associatedContractList) + + @app.route('/api/v1.0/getTokenTransactions', methods=['GET']) async def getTokenTransactions(): token = request.args.get('token') @@ -318,6 +373,58 @@ async def getTokenTransactions(): return jsonify(result='ok', token=token, transactions=rowarray_list) +@app.route('/api/v2/tokenTransactions', methods=['GET']) +async def tokenTransactions(): + token = request.args.get('token') + senderFloAddress = request.args.get('senderFloAddress') + destFloAddress = request.args.get('destFloAddress') + limit = request.args.get('limit') + + if token is None: + return jsonify(result='error', description='token name hasnt been passed') + + 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 jsonify(result='error', description='token doesn\'t exist') + + if senderFloAddress and not destFloAddress: + if limit is None: + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(senderFloAddress)) + else: + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(senderFloAddress, limit)) + elif not senderFloAddress and destFloAddress: + if limit is None: + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(destFloAddress)) + else: + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(destFloAddress, limit)) + elif senderFloAddress and destFloAddress: + if limit is None: + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(senderFloAddress, destFloAddress)) + else: + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(senderFloAddress, destFloAddress, limit)) + + else: + if limit is None: + c.execute( + 'SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT 100') + else: + c.execute( + 'SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT {}'.format(limit)) + transactionJsonData = c.fetchall() + conn.close() + rowarray_list = {} + for row in transactionJsonData: + temp = {} + temp['transactionDetails'] = json.loads(row[0]) + temp['parsedFloData'] = json.loads(row[1]) + rowarray_list[temp['transactionDetails']['txid']] = temp + return jsonify(result='ok', token=token, transactions=rowarray_list) + + @app.route('/api/v1.0/getTokenBalances', methods=['GET']) async def getTokenBalances(): token = request.args.get('token') @@ -342,6 +449,30 @@ async def getTokenBalances(): return jsonify(result='ok', token=token, balances=returnList) +@app.route('/api/v2/tokenBalances', methods=['GET']) +async def tokenBalances(): + token = request.args.get('token') + if token is None: + return jsonify(result='error', description='token name hasnt been passed') + + dblocation = dbfolder + '/tokens/' + str(token) + '.db' + if os.path.exists(dblocation): + conn = sqlite3.connect(dblocation) + c = conn.cursor() + else: + return jsonify(result='error', description='token doesn\'t exist') + c.execute( + 'SELECT address,SUM(transferBalance) FROM activeTable GROUP BY address') + addressBalances = c.fetchall() + + returnList = {} + + for address in addressBalances: + returnList[address[0]] = address[1] + + return jsonify(result='ok', token=token, balances=returnList) + + # FLO address APIs @app.route('/api/v1.0/getFloAddressInfo', methods=['GET']) async def getFloAddressInfo(): @@ -403,6 +534,66 @@ async def getFloAddressInfo(): incorporatedSmartContracts=None) +@app.route('/api/v2/floAddressInfo', methods=['GET']) +async def floAddressInfo(): + 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() + + c.execute( + f"select contractName, status, tokenIdentification, contractType, transactionHash, blockNumber, blockHash from activecontracts where contractAddress='{floAddress}'") + incorporatedContracts = 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[token] = tempdict + + else: + # Address is not associated with any token + return jsonify(result='error', description='FLO address is not associated with any tokens') + + if len(incorporatedContracts) != 0: + incorporatedSmartContracts = [] + for contract in incorporatedContracts: + tempdict = {} + tempdict['contractName'] = contract[0] + tempdict['contractAddress'] = floAddress + tempdict['status'] = contract[1] + tempdict['tokenIdentification'] = contract[2] + tempdict['contractType'] = contract[3] + tempdict['transactionHash'] = contract[4] + tempdict['blockNumber'] = contract[5] + tempdict['blockHash'] = contract[6] + incorporatedSmartContracts.append(tempdict) + + return jsonify(result='ok', floAddress=floAddress, floAddressBalances=detailList, incorporatedSmartContracts=incorporatedContracts) + else: + return jsonify(result='ok', floAddress=floAddress, floAddressBalances=detailList, + incorporatedSmartContracts=None) + + @app.route('/api/v1.0/getFloAddressBalance', methods=['GET']) async def getAddressBalance(): floAddress = request.args.get('floAddress') @@ -456,6 +647,59 @@ async def getAddressBalance(): return jsonify(result='ok', token=token, floAddress=floAddress, balance=balance) +@app.route('/api/v2/floAddressBalance', methods=['GET']) +async def floAddressBalance(): + floAddress = request.args.get('floAddress') + token = request.args.get('token') + + if floAddress is None: + return jsonify(result='error', description='floAddress hasn\'t been passed') + + if token is None: + 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[token] = tempdict + + return jsonify(result='ok', floAddress=floAddress, floAddressBalances=detailList) + + else: + # Address is not associated with any token + return jsonify(result='error', description='FLO address is not associated with any tokens') + else: + dblocation = dbfolder + '/tokens/' + str(token) + '.db' + if os.path.exists(dblocation): + conn = sqlite3.connect(dblocation) + c = conn.cursor() + else: + return jsonify(result='error', description='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 getFloAddressTransactions(): floAddress = request.args.get('floAddress') @@ -513,6 +757,63 @@ async def getFloAddressTransactions(): return jsonify(result='error', description='No token transactions present present on this address') +@app.route('/api/v2/floAddressTransactions', methods=['GET']) +async def floAddressTransactions(): + floAddress = request.args.get('floAddress') + token = request.args.get('token') + limit = request.args.get('limit') + + if floAddress is None: + return jsonify(result='error', description='floAddress has not been passed') + + if token is None: + 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() + else: + dblocation = dbfolder + '/tokens/' + str(token) + '.db' + if os.path.exists(dblocation): + tokenNames = [[str(token), ]] + else: + return jsonify(result='error', description='token doesn\'t exist') + + if len(tokenNames) != 0: + allTransactionList = {} + + for tokenname in tokenNames: + tokenname = tokenname[0] + dblocation = dbfolder + '/tokens/' + str(tokenname) + '.db' + if os.path.exists(dblocation): + tempdict = {} + conn = sqlite3.connect(dblocation) + c = conn.cursor() + if limit is None: + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" OR destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format( + floAddress, floAddress)) + else: + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" OR destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format( + floAddress, floAddress, limit)) + transactionJsonData = c.fetchall() + conn.close() + + for row in transactionJsonData: + temp = {} + temp['transactionDetails'] = json.loads(row[0]) + temp['parsedFloData'] = json.loads(row[1]) + allTransactionList[temp['transactionDetails'] + ['txid']] = temp + + if token is None: + return jsonify(result='ok', floAddress=floAddress, transactions=allTransactionList) + else: + return jsonify(result='ok', floAddress=floAddress, transactions=allTransactionList, token=token) + else: + return jsonify(result='error', description='No token transactions present present on this address') + + # SMART CONTRACT APIs @app.route('/api/v1.0/getSmartContractList', methods=['GET']) async def getContractList(): @@ -607,7 +908,7 @@ async def getContractList(): return jsonify(smartContracts=contractList, result='ok') -@app.route('/api/v2.0/smartContractList', methods=['GET']) +@app.route('/api/v2/smartContractList', methods=['GET']) async def getContractList_v2(): contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') @@ -734,7 +1035,7 @@ async def getContractInfo(): return jsonify(result='error', details='Smart Contract with the given name doesn\'t exist') -@app.route('/api/v2.0/smartContractInfo', methods=['GET']) +@app.route('/api/v2/smartContractInfo', methods=['GET']) async def getContractInfo_v2(): contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') @@ -864,7 +1165,7 @@ async def getcontractparticipants(): return jsonify(result='error', description='Smart Contract with the given name doesn\'t exist') -@app.route('/api/v2.0/smartContractParticipants', methods=['GET']) +@app.route('/api/v2/smartContractParticipants', methods=['GET']) async def getcontractparticipants_v2(): contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') @@ -1133,6 +1434,207 @@ async def getParticipantDetails(): return jsonify(result='error', description='System error. System db is missing') +@app.route('/api/v2/participantDetails', methods=['GET']) +async def participantDetails(): + floAddress = request.args.get('floAddress') + contractName = request.args.get('contractName') + contractAddress = request.args.get('contractAddress') + + if floAddress is None: + return jsonify(result='error', description='FLO address hasn\'t been passed') + dblocation = os.path.join(dbfolder, 'system.db') + + if (contractName and contractAddress is None) or (contractName is None and contractAddress): + return jsonify(result='error', description='pass both, contractName and contractAddress as url parameters') + + #if os.path.isfile(dblocation) and os.path.isfile(contract_db): + if os.path.isfile(dblocation): + # Make db connection and fetch data + conn = sqlite3.connect(dblocation) + c = conn.cursor() + + if contractName is not None: + c.execute(f'SELECT * FROM contractAddressMapping WHERE address="{floAddress}" AND addressType="participant" AND contractName="{contractName}" AND contractAddress="{contractAddress}"') + else: + c.execute(f'SELECT * FROM contractAddressMapping WHERE address="{floAddress}" AND addressType="participant"') + participant_address_contracts = c.fetchall() + + if len(participant_address_contracts) != 0: + participationDetailsList = [] + for contract in participant_address_contracts: + detailsDict = {} + contract_db = os.path.join(dbfolder, 'smartContracts', f"{contract[3]}-{contract[4]}.db") + # Make db connection and fetch contract structure + conn = sqlite3.connect(contract_db) + c = conn.cursor() + + # Get details of the type of Smart Contract + c.execute('SELECT attribute,value FROM contractstructure') + result = c.fetchall() + + contractStructure = {} + conditionDict = {} + counter = 0 + for item in result: + 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']=='continuos-event' and contractStructure['subtype']=='tokenswap': + # normal result + swap details + # what is a api detail + c.execute('SELECT * FROM contractparticipants WHERE participantAddress=?',(floAddress,)) + participant_details = c.fetchall() + + if len(participant_details) > 0: + participationList = [] + for participation in participant_details: + c.execute("SELECT value FROM contractstructure WHERE attribute='selling_token'") + structure = c.fetchall() + detailsDict['participationAddress'] = floAddress + detailsDict['participationAmount'] = participation[2] + detailsDict['receivedAmount'] = float(participation[3]) + detailsDict['participationToken'] = contractStructure['accepting_token'] + detailsDict['receivedToken'] = contractStructure['selling_token'] + detailsDict['swapPrice_received_to_participation'] = float(participation[7]) + detailsDict['transactionHash'] = participation[4] + detailsDict['blockNumber'] = participation[5] + detailsDict['blockHash'] = participation[6] + participationList.append(detailsDict) + + participationDetailsList.append(participationList) + + elif contractStructure['contractType']=='one-time-event' and 'payeeAddress' in contractStructure.keys(): + # normal results + conn = sqlite3.connect(dblocation) + c = conn.cursor() + detailsDict = {} + detailsDict['contractName'] = contract[3] + detailsDict['contractAddress'] = contract[4] + detailsDict['tokenAmount'] = contract[5] + detailsDict['transactionHash'] = contract[6] + + c.execute(f"select status, tokenIdentification, contractType, blockNumber, blockHash, incorporationDate, expiryDate, closeDate from activecontracts where contractName='{detailsDict['contractName']}' and contractAddress='{detailsDict['contractAddress']}'") + temp = c.fetchall() + detailsDict['status'] = temp[0][0] + detailsDict['tokenIdentification'] = temp[0][1] + detailsDict['contractType'] = temp[0][2] + detailsDict['blockNumber'] = temp[0][3] + detailsDict['blockHash'] = temp[0][4] + detailsDict['incorporationDate'] = temp[0][5] + if temp[0][6]: + detailsDict['expiryDate'] = temp[0][6] + if temp[0][7]: + detailsDict['closeDate'] = temp[0][7] + + # check if the contract has been closed + contractDbName = '{}-{}.db'.format(detailsDict['contractName'].strip(), detailsDict['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() + contractStructure = {} + conditionDict = {} + counter = 0 + for item in result: + 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 'payeeAddress' in contractStructure: + # contract is of the type external trigger + # check if the contract has been closed + c.execute(f"SELECT tokenAmount FROM contractparticipants where participantAddress='{floAddress}'") + result = c.fetchall() + conn.close() + detailsDict['tokenAmount'] = result[0][0] + + elif contractStructure['contractType']=='one-time-event' and 'exitconditions' in contractStructure.keys(): + # normal results + winning/losing details + conn = sqlite3.connect(dblocation) + c = conn.cursor() + detailsDict = {} + detailsDict['contractName'] = contract[3] + detailsDict['contractAddress'] = contract[4] + detailsDict['tokenAmount'] = contract[5] + detailsDict['transactionHash'] = contract[6] + + c.execute(f"select status, tokenIdentification, contractType, blockNumber, blockHash, incorporationDate, expiryDate, closeDate from activecontracts where contractName='{detailsDict['contractName']}' and contractAddress='{detailsDict['contractAddress']}'") + temp = c.fetchall() + detailsDict['status'] = temp[0][0] + detailsDict['tokenIdentification'] = temp[0][1] + detailsDict['contractType'] = temp[0][2] + detailsDict['blockNumber'] = temp[0][3] + detailsDict['blockHash'] = temp[0][4] + detailsDict['incorporationDate'] = temp[0][5] + if temp[0][6]: + detailsDict['expiryDate'] = temp[0][6] + if temp[0][7]: + detailsDict['closeDate'] = temp[0][7] + + # check if the contract has been closed + contractDbName = '{}-{}.db'.format(detailsDict['contractName'].strip(), detailsDict['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() + contractStructure = {} + conditionDict = {} + counter = 0 + for item in result: + 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 'exitconditions' in contractStructure: + # contract is of the type external trigger + # check if the contract has been closed + c.execute('select * from contractTransactionHistory where transactionType="trigger"') + trigger = c.fetchall() + + if detailsDict['status'] == 'closed': + c.execute(f"SELECT userChoice, winningAmount FROM contractparticipants where participantAddress='{floAddress}'") + result = c.fetchall() + conn.close() + detailsDict['userChoice'] = result[0][0] + detailsDict['winningAmount'] = result[0][1] + else: + c.execute(f"SELECT userChoice FROM contractparticipants where participantAddress='{floAddress}'") + result = c.fetchall() + conn.close() + detailsDict['userChoice'] = result[0][0] + + participationDetailsList.append(detailsDict) + + return jsonify(result='ok', floAddress=floAddress, type='participant', participatedContracts=participationDetailsList) + + else: + return jsonify(result='error', description='Address hasn\'t participated in any other contract') + else: + return jsonify(result='error', description='System error. System db is missing') + + @app.route('/api/v1.0/getSmartContractTransactions', methods=['GET']) async def getsmartcontracttransactions(): contractName = request.args.get('contractName') @@ -1168,9 +1670,52 @@ async def getsmartcontracttransactions(): return jsonify(result='error', description='Smart Contract with the given name doesn\'t exist') +@app.route('/api/v2/smartContractTransactions', methods=['GET']) +async def smartcontracttransactions(): + contractName = request.args.get('contractName') + contractAddress = request.args.get('contractAddress') + + if contractName is None: + return jsonify(result='error', description='Smart Contract\'s name hasn\'t been passed') + + if contractAddress is None: + return jsonify(result='error', description='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 jsonData, parsedFloData from contractTransactionHistory') + result = c.fetchall() + conn.close() + returnval = {} + + for item in result: + temp = {} + temp['transactionDetails'] = json.loads(item[0]) + temp['parsedFloData'] = json.loads(item[1]) + returnval[temp['transactionDetails']['txid']] = temp + + return jsonify(result='ok') + else: + return jsonify(result='error', description='Smart Contract with the given name doesn\'t exist') + + @app.route('/api/v1.0/getBlockDetails/', methods=['GET']) async def getblockdetails(blockdetail): - + blockJson = blockdetailhelper(blockdetail) + if len(blockJson) != 0: + blockJson = json.loads(blockJson[0][0]) + return jsonify(result='ok', blockDetails=blockJson) + else: + return jsonify(result='error', description='Block doesn\'t exist in database') + + +@app.route('/api/v2/blockDetails/', methods=['GET']) +async def blockdetails(blockdetail): blockJson = blockdetailhelper(blockdetail) if len(blockJson) != 0: blockJson = json.loads(blockJson[0][0]) @@ -1193,8 +1738,8 @@ async def gettransactiondetails(transactionHash): return jsonify(result='error', description='Transaction doesn\'t exist in database') -@app.route('/api/v2.0/getTransactionDetails/', methods=['GET']) -async def gettransactiondetails1(transactionHash): +@app.route('/api/v2/transactionDetails/', methods=['GET']) +async def transactiondetails1(transactionHash): transactionJsonData = transactiondetailhelper(transactionHash) if len(transactionJsonData) != 0: transactionJson = json.loads(transactionJsonData[0][0]) @@ -1273,6 +1818,46 @@ async def gettransactiondetails1(transactionHash): @app.route('/api/v1.0/getLatestTransactionDetails', methods=['GET']) async def getLatestTransactionDetails(): + numberOfLatestBlocks = request.args.get('numberOfLatestBlocks') + + 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' + + if numberOfLatestBlocks is not None: + c.execute('SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT {}) ORDER BY id ASC;'.format(int(numberOfLatestBlocks))) + 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[3]) + tx_parsed_details['parsedFloData'] = json.loads(item[5]) + tx_parsed_details['parsedFloData']['transactionType'] = item[4] + tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) + tempdict[json.loads(item[3])['txid']] = tx_parsed_details + else: + c.execute('''SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT 100) 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[3]) + tx_parsed_details['parsedFloData'] = json.loads(item[5]) + tx_parsed_details['parsedFloData']['transactionType'] = item[4] + tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) + tempdict[json.loads(item[3])['txid']] = tx_parsed_details + return jsonify(result='ok', latestTransactions=tempdict) + + +@app.route('/api/v2/latestTransactionDetails', methods=['GET']) +async def latestTransactionDetails(): numberOfLatestBlocks = request.args.get('numberOfLatestBlocks') @@ -1314,7 +1899,6 @@ async def getLatestTransactionDetails(): @app.route('/api/v1.0/getLatestBlockDetails', methods=['GET']) async def getLatestBlockDetails(): - limit = request.args.get('limit') dblocation = dbfolder + '/latestCache.db' @@ -1337,6 +1921,29 @@ async def getLatestBlockDetails(): return jsonify(result='ok', latestBlocks=tempdict) +@app.route('/api/v2/latestBlockDetails', methods=['GET']) +async def latestBlockDetails(): + limit = request.args.get('limit') + 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' + + if limit is None: + c.execute('''SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT 4) ORDER BY id ASC;''') + else: + int(limit) + c.execute('SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT {}) ORDER BY id ASC;'.format(limit)) + latestBlocks = c.fetchall() + c.close() + tempdict = {} + for idx, item in enumerate(latestBlocks): + tempdict[json.loads(item[3])['hash']] = json.loads(item[3]) + return jsonify(result='ok', latestBlocks=tempdict) + + @app.route('/api/v1.0/getBlockTransactions/', methods=['GET']) async def getblocktransactions(blockdetail): blockJson = blockdetailhelper(blockdetail) @@ -1357,6 +1964,26 @@ async def getblocktransactions(blockdetail): return jsonify(result='error', description='Block doesn\'t exist in database') +@app.route('/api/v2/blockTransactions/', methods=['GET']) +async def blocktransactions(blockdetail): + blockJson = blockdetailhelper(blockdetail) + if len(blockJson) != 0: + blockJson = json.loads(blockJson[0][0]) + blocktxlist = blockJson['tx'] + blocktxs = {} + for i in range(len(blocktxlist)): + temptx = transactiondetailhelper(blocktxlist[i]) + transactionJson = json.loads(temptx[0][0]) + parseResult = json.loads(temptx[0][1]) + blocktxs[blocktxlist[i]] = { + "parsedFloData" : parseResult, + "transactionDetails" : transactionJson + } + return jsonify(result='ok', transactions=blocktxs, blockKeyword=blockdetail) + else: + return jsonify(result='error', description='Block doesn\'t exist in database') + + @app.route('/api/v1.0/categoriseString/') async def categoriseString(urlstring): # check if the hash is of a transaction @@ -1389,6 +2016,38 @@ async def categoriseString(urlstring): return jsonify(type='noise') +@app.route('/api/v2/categoriseString/') +async def categoriseString_v2(urlstring): + # check if the hash is of a transaction + response = requests.get('{}tx/{}'.format(apiUrl, urlstring)) + if response.status_code == 200: + return jsonify(type='transaction') + else: + response = requests.get('{}block/{}'.format(apiUrl, urlstring)) + if response.status_code == 200: + return jsonify(type='block') + else: + # check urlstring is a token name + tokenfolder = os.path.join(dbfolder, 'tokens') + onlyfiles = [f[:-3] + for f in os.listdir(tokenfolder) if os.path.isfile(os.path.join(tokenfolder, f))] + + if urlstring.lower() in onlyfiles: + return jsonify(type='token') + else: + contractfolder = os.path.join(dbfolder, 'system.db') + conn = sqlite3.connect(contractfolder) + conn.row_factory = lambda cursor, row: row[0] + c = conn.cursor() + contractList = c.execute( + 'select contractname from activeContracts').fetchall() + + if urlstring.lower() in contractList: + return jsonify(type='smartContract') + else: + return jsonify(type='noise') + + @app.route('/api/v1.0/getTokenSmartContractList', methods=['GET']) async def getTokenSmartContractList(): # list of tokens @@ -1426,6 +2085,43 @@ async def getTokenSmartContractList(): return jsonify(tokens=filelist, smartContracts=contractList, result='ok') +@app.route('/api/v2/tokenSmartContractList', methods=['GET']) +async def tokenSmartContractList(): + # list of tokens + 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]) + + # list of smart contracts + conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) + c = conn.cursor() + + contractList = [] + + 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['tokenIdentification'] = contract[4] + contractDict['contractType'] = contract[5] + contractDict['transactionHash'] = contract[6] + contractDict['blockNumber'] = contract[7] + contractDict['blockHash'] = contract[8] + contractDict['incorporationDate'] = contract[9] + if contract[10]: + contractDict['expiryDate'] = contract[10] + if contract[11]: + contractDict['closeDate'] = contract[11] + + contractList.append(contractDict) + + return jsonify(tokens=filelist, smartContracts=contractList, result='ok') + + class ServerSentEvent: def __init__( @@ -1527,13 +2223,28 @@ async def getPriceData(): return jsonify(prices=prices, result='ok') +@app.route('/api/v2/prices', methods=['GET']) +async def priceData(): + # read system.db for price data + conn = sqlite3.connect('system.db') + c = conn.cursor() + ratepairs = c.execute('select ratepair, price from ratepairs') + ratepairs = ratepairs.fetchall() + prices = {} + + for ratepair in ratepairs: + ratepair = list(ratepair) + prices[ratepair[0]] = ratepair[1] + + return jsonify(prices=prices, result='ok') + + @app.route('/api/test') async def test_msg(): return jsonify('Welcome to Ranchi Mall API') # if system.db isn't present, initialize it - if not os.path.isfile(f"system.db"): # create an empty db conn = sqlite3.connect('system.db') @@ -1559,4 +2270,4 @@ scheduler.start() atexit.register(lambda: scheduler.shutdown()) if __name__ == "__main__": - app.run(debug=True, host='0.0.0.0', port=5009) \ No newline at end of file + app.run(debug=debug_status, host='0.0.0.0', port=5009) \ No newline at end of file From e4dd64f7a1aa5728895c49713d82bc128405e9d7 Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Wed, 18 Jan 2023 20:19:47 +0000 Subject: [PATCH 05/46] Added response codes to v2 --- ranchimallflo_api.py | 249 ++++++++++++++++++------------------------- 1 file changed, 106 insertions(+), 143 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index f233de5..4289400 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -150,6 +150,17 @@ def fetchContractStructure(contractName, contractAddress): contractStructure['exitconditions'] = conditionDict del counter, conditionDict, c conn.close() + + if 'contractAmount' in contractStructure: + contractStructure['contractAmount'] = float(contractStructure['contractAmount']) + if 'payeeAddress' in contractStructure: + contractStructure['payeeAddress'] = json.loads(contractStructure['payeeAddress']) + if 'maximumsubscriptionamount' in contractStructure: + contractStructure['maximumsubscriptionamount'] = float(contractStructure['maximumsubscriptionamount']) + if 'minimumsubscriptionamount' in contractStructure: + contractStructure['minimumsubscriptionamount'] = float(contractStructure['minimumsubscriptionamount']) + if 'price' in contractStructure: + contractStructure['price'] = float(contractStructure['price']) return contractStructure else: return 0 @@ -207,22 +218,20 @@ async def info(): validatedTransactionCount = c.execute('select count(distinct transactionHash) from latestTransactions').fetchall()[0][0] conn.close() - return jsonify(systemAddressCount=tokenAddressCount, systemBlockCount=validatedBlockCount, systemTransactionCount=validatedTransactionCount, systemSmartContractCount=contractCount, systemTokenCount=tokenCount, lastscannedblock=lastscannedblock, result='ok') + return jsonify(systemAddressCount=tokenAddressCount, systemBlockCount=validatedBlockCount, systemTransactionCount=validatedTransactionCount, systemSmartContractCount=contractCount, systemTokenCount=tokenCount, lastscannedblock=lastscannedblock), 200 - -# FLO TOKEN APIs @app.route('/api/v1.0/broadcastTx/') async def broadcastTx(raw_transaction_hash): p1 = subprocess.run(['flo-cli',f"-datadir={FLO_DATA_DIR}",'sendrawtransaction',raw_transaction_hash], capture_output=True) return jsonify(args=p1.args,returncode=p1.returncode,stdout=p1.stdout.decode(),stderr=p1.stderr.decode()) - @app.route('/api/v2/broadcastTx/') async def broadcastTx_v2(raw_transaction_hash): p1 = subprocess.run(['flo-cli',f"-datadir={FLO_DATA_DIR}",'sendrawtransaction',raw_transaction_hash], capture_output=True) - return jsonify(args=p1.args,returncode=p1.returncode,stdout=p1.stdout.decode(),stderr=p1.stderr.decode()) + return jsonify(args=p1.args,returncode=p1.returncode,stdout=p1.stdout.decode(),stderr=p1.stderr.decode()), 200 +# FLO TOKEN APIs @app.route('/api/v1.0/getTokenList', methods=['GET']) async def getTokenList(): filelist = [] @@ -240,7 +249,7 @@ async def tokenList(): if os.path.isfile(os.path.join(dbfolder, 'tokens', item)): filelist.append(item[:-3]) - return jsonify(tokens=filelist, result='ok') + return jsonify(tokens=filelist), 200 @app.route('/api/v1.0/getTokenInfo', methods=['GET']) @@ -282,19 +291,17 @@ async def getTokenInfo(): activeAddress_no=numberOf_distinctAddresses, totalTransactions=numberOf_transactions, associatedSmartContracts=associatedContractList) -@app.route('/api/v2/tokenInfo', methods=['GET']) -async def tokenInfo(): - token = request.args.get('token') - +@app.route('/api/v2/tokenInfo/', methods=['GET']) +async def tokenInfo(token): if token is None: - return jsonify(result='error', description='token name hasnt been passed') + return jsonify(description='token name hasnt been passed'), 400 dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() else: - return jsonify(result='error', description='token doesn\'t exist') + return jsonify(description='token doesn\'t exist'), 404 c.execute('SELECT * FROM transactionHistory WHERE id=1') incorporationRow = c.fetchall()[0] c.execute('SELECT COUNT (DISTINCT address) FROM activeTable') @@ -316,9 +323,7 @@ async def tokenInfo(): tempdict['transactionHash'] = item[4] associatedContractList.append(tempdict) - return jsonify(result='ok', token=token, incorporationAddress=incorporationRow[1], tokenSupply=incorporationRow[3], - time=incorporationRow[6], blockchainReference=incorporationRow[7], - activeAddress_no=numberOf_distinctAddresses, totalTransactions=numberOf_transactions, associatedSmartContracts=associatedContractList) + return jsonify(token=token, incorporationAddress=incorporationRow[1], tokenSupply=incorporationRow[3], time=incorporationRow[6], blockchainReference=incorporationRow[7], activeAddress_no=numberOf_distinctAddresses, totalTransactions=numberOf_transactions, associatedSmartContracts=associatedContractList), 200 @app.route('/api/v1.0/getTokenTransactions', methods=['GET']) @@ -373,23 +378,22 @@ async def getTokenTransactions(): return jsonify(result='ok', token=token, transactions=rowarray_list) -@app.route('/api/v2/tokenTransactions', methods=['GET']) -async def tokenTransactions(): - token = request.args.get('token') +@app.route('/api/v2/tokenTransactions/', methods=['GET']) +async def tokenTransactions(token): + if token is None: + return jsonify(description='token name hasnt been passed'), 400 + senderFloAddress = request.args.get('senderFloAddress') destFloAddress = request.args.get('destFloAddress') limit = request.args.get('limit') - if token is None: - return jsonify(result='error', description='token name hasnt been passed') - 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 jsonify(result='error', description='token doesn\'t exist') + return jsonify(description='token doesn\'t exist'), 404 if senderFloAddress and not destFloAddress: if limit is None: @@ -409,11 +413,9 @@ async def tokenTransactions(): else: if limit is None: - c.execute( - 'SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT 100') + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT 100') else: - c.execute( - 'SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT {}'.format(limit)) + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT {}'.format(limit)) transactionJsonData = c.fetchall() conn.close() rowarray_list = {} @@ -422,7 +424,7 @@ async def tokenTransactions(): temp['transactionDetails'] = json.loads(row[0]) temp['parsedFloData'] = json.loads(row[1]) rowarray_list[temp['transactionDetails']['txid']] = temp - return jsonify(result='ok', token=token, transactions=rowarray_list) + return jsonify(token=token, transactions=rowarray_list), 200 @app.route('/api/v1.0/getTokenBalances', methods=['GET']) @@ -449,37 +451,33 @@ async def getTokenBalances(): return jsonify(result='ok', token=token, balances=returnList) -@app.route('/api/v2/tokenBalances', methods=['GET']) -async def tokenBalances(): - token = request.args.get('token') +@app.route('/api/v2/tokenBalances/', methods=['GET']) +async def tokenBalances(token): if token is None: - return jsonify(result='error', description='token name hasnt been passed') + return jsonify(description='token name hasnt been passed'), 400 dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() else: - return jsonify(result='error', description='token doesn\'t exist') - c.execute( - 'SELECT address,SUM(transferBalance) FROM activeTable GROUP BY address') + return jsonify(description='token doesn\'t exist'), 404 + c.execute('SELECT address,SUM(transferBalance) FROM activeTable GROUP BY address') addressBalances = c.fetchall() returnList = {} - for address in addressBalances: returnList[address[0]] = address[1] - return jsonify(result='ok', token=token, balances=returnList) + return jsonify(token=token, balances=returnList), 200 -# FLO address APIs +# FLO Address APIs @app.route('/api/v1.0/getFloAddressInfo', methods=['GET']) async def getFloAddressInfo(): floAddress = request.args.get('floAddress') - if floAddress is None: - return jsonify(result='error', description='floAddress hasn\'t been passed') + return jsonify(description='floAddress hasn\'t been passed'), 400 dblocation = dbfolder + '/system.db' if os.path.exists(dblocation): @@ -534,28 +532,22 @@ async def getFloAddressInfo(): incorporatedSmartContracts=None) -@app.route('/api/v2/floAddressInfo', methods=['GET']) -async def floAddressInfo(): - floAddress = request.args.get('floAddress') - +@app.route('/api/v2/floAddressInfo/', methods=['GET']) +async def floAddressInfo(floAddress): if floAddress is None: - return jsonify(result='error', description='floAddress hasn\'t been passed') + return jsonify(description='floAddress hasn\'t been passed'), 400 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)) + c.execute(f'SELECT token FROM tokenAddressMapping WHERE tokenAddress="{floAddress}"') tokenNames = c.fetchall() - - c.execute( - f"select contractName, status, tokenIdentification, contractType, transactionHash, blockNumber, blockHash from activecontracts where contractAddress='{floAddress}'") + c.execute(f"SELECT contractName, status, tokenIdentification, contractType, transactionHash, blockNumber, blockHash FROM activecontracts WHERE contractAddress='{floAddress}'") incorporatedContracts = c.fetchall() if len(tokenNames) != 0: detailList = {} - for token in tokenNames: token = token[0] dblocation = dbfolder + '/tokens/' + str(token) + '.db' @@ -563,16 +555,14 @@ async def floAddressInfo(): tempdict = {} conn = sqlite3.connect(dblocation) c = conn.cursor() - c.execute( - 'SELECT SUM(transferBalance) FROM activeTable WHERE address="{}"'.format(floAddress)) + c.execute(f'SELECT SUM(transferBalance) FROM activeTable WHERE address="{floAddress}"') balance = c.fetchall()[0][0] tempdict['balance'] = balance tempdict['token'] = token detailList[token] = tempdict - else: # Address is not associated with any token - return jsonify(result='error', description='FLO address is not associated with any tokens') + return jsonify(description='FLO address is not associated with any tokens'), 404 if len(incorporatedContracts) != 0: incorporatedSmartContracts = [] @@ -587,11 +577,9 @@ async def floAddressInfo(): tempdict['blockNumber'] = contract[5] tempdict['blockHash'] = contract[6] incorporatedSmartContracts.append(tempdict) - - return jsonify(result='ok', floAddress=floAddress, floAddressBalances=detailList, incorporatedSmartContracts=incorporatedContracts) else: - return jsonify(result='ok', floAddress=floAddress, floAddressBalances=detailList, - incorporatedSmartContracts=None) + incorporatedContracts=None + return jsonify(floAddress=floAddress, floAddressBalances=detailList, incorporatedSmartContracts=None), 200 @app.route('/api/v1.0/getFloAddressBalance', methods=['GET']) @@ -647,26 +635,22 @@ async def getAddressBalance(): return jsonify(result='ok', token=token, floAddress=floAddress, balance=balance) -@app.route('/api/v2/floAddressBalance', methods=['GET']) -async def floAddressBalance(): - floAddress = request.args.get('floAddress') - token = request.args.get('token') - +@app.route('/api/v2/floAddressBalance/', methods=['GET']) +async def floAddressBalance(floAddress): if floAddress is None: - return jsonify(result='error', description='floAddress hasn\'t been passed') + return jsonify(description='floAddress hasn\'t been passed'), 400 + token = request.args.get('token') if token is None: 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)) + c.execute(f'SELECT token FROM tokenAddressMapping WHERE tokenAddress="{floAddress}"') tokenNames = c.fetchall() if len(tokenNames) != 0: detailList = {} - for token in tokenNames: token = token[0] dblocation = dbfolder + '/tokens/' + str(token) + '.db' @@ -674,30 +658,26 @@ async def floAddressBalance(): tempdict = {} conn = sqlite3.connect(dblocation) c = conn.cursor() - c.execute( - 'SELECT SUM(transferBalance) FROM activeTable WHERE address="{}"'.format(floAddress)) + c.execute(f'SELECT SUM(transferBalance) FROM activeTable WHERE address="{floAddress}"') balance = c.fetchall()[0][0] tempdict['balance'] = balance tempdict['token'] = token detailList[token] = tempdict - - return jsonify(result='ok', floAddress=floAddress, floAddressBalances=detailList) - + return jsonify(floAddress=floAddress, floAddressBalances=detailList), 200 else: # Address is not associated with any token - return jsonify(result='error', description='FLO address is not associated with any tokens') + return jsonify(floAddress=floAddress, floAddressBalances={}), 200 else: dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() else: - return jsonify(result='error', description='token doesn\'t exist') - c.execute( - 'SELECT SUM(transferBalance) FROM activeTable WHERE address="{}"'.format(floAddress)) + return jsonify(description="token doesn't exist"), 404 + c.execute(f'SELECT SUM(transferBalance) FROM activeTable WHERE address="{floAddress}"') balance = c.fetchall()[0][0] conn.close() - return jsonify(result='ok', token=token, floAddress=floAddress, balance=balance) + return jsonify(token=token, floAddress=floAddress, balance=balance), 200 @app.route('/api/v1.0/getFloAddressTransactions', methods=['GET']) @@ -757,15 +737,13 @@ async def getFloAddressTransactions(): return jsonify(result='error', description='No token transactions present present on this address') -@app.route('/api/v2/floAddressTransactions', methods=['GET']) -async def floAddressTransactions(): - floAddress = request.args.get('floAddress') +@app.route('/api/v2/floAddressTransactions/', methods=['GET']) +async def floAddressTransactions(floAddress): + if floAddress is None: + return jsonify(description='floAddress has not been passed'), 400 + token = request.args.get('token') limit = request.args.get('limit') - - if floAddress is None: - return jsonify(result='error', description='floAddress has not been passed') - if token is None: dblocation = dbfolder + '/system.db' if os.path.exists(dblocation): @@ -778,7 +756,7 @@ async def floAddressTransactions(): if os.path.exists(dblocation): tokenNames = [[str(token), ]] else: - return jsonify(result='error', description='token doesn\'t exist') + return jsonify(description='token doesn\'t exist'), 404 if len(tokenNames) != 0: allTransactionList = {} @@ -791,11 +769,9 @@ async def floAddressTransactions(): conn = sqlite3.connect(dblocation) c = conn.cursor() if limit is None: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" OR destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format( - floAddress, floAddress)) + c.execute(f'SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{floAddress}" OR destFloAddress="{floAddress}" ORDER BY id DESC LIMIT 100') else: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" OR destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format( - floAddress, floAddress, limit)) + c.execute(f'SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{floAddress}" OR destFloAddress="{floAddress}" ORDER BY id DESC LIMIT {limit}') transactionJsonData = c.fetchall() conn.close() @@ -803,15 +779,14 @@ async def floAddressTransactions(): temp = {} temp['transactionDetails'] = json.loads(row[0]) temp['parsedFloData'] = json.loads(row[1]) - allTransactionList[temp['transactionDetails'] - ['txid']] = temp + allTransactionList[temp['transactionDetails']['txid']] = temp if token is None: - return jsonify(result='ok', floAddress=floAddress, transactions=allTransactionList) + return jsonify(floAddress=floAddress, transactions=allTransactionList), 200 else: - return jsonify(result='ok', floAddress=floAddress, transactions=allTransactionList, token=token) + return jsonify(floAddress=floAddress, transactions=allTransactionList, token=token), 200 else: - return jsonify(result='error', description='No token transactions present present on this address') + return jsonify(floAddress=floAddress, transactions={}, token=token), 200 # SMART CONTRACT APIs @@ -1040,9 +1015,9 @@ async def getContractInfo_v2(): contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') if contractName is None: - return jsonify(result='error', description='Smart Contract\'s name hasn\'t been passed') + return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 if contractAddress is None: - return jsonify(result='error', description='Smart Contract\'s address hasn\'t been passed') + return jsonify(description='Smart Contract\'s address hasn\'t been passed'), 400 contractName = contractName.strip() contractAddress = contractAddress.strip() @@ -1079,9 +1054,9 @@ async def getContractInfo_v2(): elif contractStructure['contractType'] == 'one-time-event' and 'payeeAddress' in contractStructure.keys(): pass - return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, contractInfo=returnval) + return jsonify(contractName=contractName, contractAddress=contractAddress, contractInfo=returnval), 200 else: - return jsonify(result='error', details='Smart Contract with the given name doesn\'t exist') + return jsonify(details='Smart Contract with the given name doesn\'t exist'), 404 @app.route('/api/v1.0/getSmartContractParticipants', methods=['GET']) @@ -1171,9 +1146,9 @@ async def getcontractparticipants_v2(): contractAddress = request.args.get('contractAddress') if contractName is None: - return jsonify(result='error', description='Smart Contract\'s name hasn\'t been passed') + return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 if contractAddress is None: - return jsonify(result='error', description='Smart Contract\'s address hasn\'t been passed') + return jsonify(description='Smart Contract\'s address hasn\'t been passed'), 400 contractName = contractName.strip() contractAddress = contractAddress.strip() @@ -1228,9 +1203,9 @@ async def getcontractparticipants_v2(): } conn.close() - return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, participantInfo=returnval) + return jsonify(contractName=contractName, contractAddress=contractAddress, participantInfo=returnval), 200 else: - return jsonify(result='error', description='Smart Contract with the given name doesn\'t exist') + return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404 @app.route('/api/v1.0/getParticipantDetails', methods=['GET']) @@ -1434,18 +1409,17 @@ async def getParticipantDetails(): return jsonify(result='error', description='System error. System db is missing') -@app.route('/api/v2/participantDetails', methods=['GET']) +@app.route('/api/v2/participantDetails/', methods=['GET']) async def participantDetails(): - floAddress = request.args.get('floAddress') + if floAddress is None: + return jsonify(description='FLO address hasn\'t been passed'), 400 contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') - if floAddress is None: - return jsonify(result='error', description='FLO address hasn\'t been passed') dblocation = os.path.join(dbfolder, 'system.db') if (contractName and contractAddress is None) or (contractName is None and contractAddress): - return jsonify(result='error', description='pass both, contractName and contractAddress as url parameters') + return jsonify(description='pass both, contractName and contractAddress as url parameters'), 400 #if os.path.isfile(dblocation) and os.path.isfile(contract_db): if os.path.isfile(dblocation): @@ -1627,12 +1601,11 @@ async def participantDetails(): participationDetailsList.append(detailsDict) - return jsonify(result='ok', floAddress=floAddress, type='participant', participatedContracts=participationDetailsList) - + return jsonify(floAddress=floAddress, type='participant', participatedContracts=participationDetailsList), 200 else: - return jsonify(result='error', description='Address hasn\'t participated in any other contract') + return jsonify(description="Address hasn't participated in any other contract"), 404 else: - return jsonify(result='error', description='System error. System db is missing') + return jsonify(description='System error. System db is missing'), 404 @app.route('/api/v1.0/getSmartContractTransactions', methods=['GET']) @@ -1674,12 +1647,10 @@ async def getsmartcontracttransactions(): async def smartcontracttransactions(): contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') - if contractName is None: - return jsonify(result='error', description='Smart Contract\'s name hasn\'t been passed') - + return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 if contractAddress is None: - return jsonify(result='error', description='Smart Contract\'s address hasn\'t been passed') + return jsonify(description='Smart Contract\'s address hasn\'t been passed'), 400 contractDbName = '{}-{}.db'.format(contractName.strip(), contractAddress.strip()) filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) @@ -1699,9 +1670,9 @@ async def smartcontracttransactions(): temp['parsedFloData'] = json.loads(item[1]) returnval[temp['transactionDetails']['txid']] = temp - return jsonify(result='ok') + return jsonify(contractName=contractName, contractAddress=contractAddress, contractTransactions=returnval), 200 else: - return jsonify(result='error', description='Smart Contract with the given name doesn\'t exist') + return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404 @app.route('/api/v1.0/getBlockDetails/', methods=['GET']) @@ -1719,9 +1690,9 @@ async def blockdetails(blockdetail): blockJson = blockdetailhelper(blockdetail) if len(blockJson) != 0: blockJson = json.loads(blockJson[0][0]) - return jsonify(result='ok', blockDetails=blockJson) + return jsonify(blockDetails=blockJson), 200 else: - return jsonify(result='error', description='Block doesn\'t exist in database') + return jsonify(description='Block doesn\'t exist in database'), 404 @app.route('/api/v1.0/getTransactionDetails/', methods=['GET']) @@ -1811,9 +1782,9 @@ async def transactiondetails1(transactionHash): if winningAmount[0][0] is not None: operationDetails['winningAmount'] = winningAmount[0][0] - return jsonify(parsedFloData=parseResult, transactionDetails=transactionJson, transactionHash=transactionHash, operation=operation, operationDetails=operationDetails, result='ok') + return jsonify(parsedFloData=parseResult, transactionDetails=transactionJson, transactionHash=transactionHash, operation=operation, operationDetails=operationDetails), 200 else: - return jsonify(result='error', description='Transaction doesn\'t exist in database') + return jsonify(description='Transaction doesn\'t exist in database'), 404 @app.route('/api/v1.0/getLatestTransactionDetails', methods=['GET']) @@ -1858,7 +1829,6 @@ async def getLatestTransactionDetails(): @app.route('/api/v2/latestTransactionDetails', methods=['GET']) async def latestTransactionDetails(): - numberOfLatestBlocks = request.args.get('numberOfLatestBlocks') dblocation = dbfolder + '/latestCache.db' @@ -1866,7 +1836,7 @@ async def latestTransactionDetails(): 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' + return jsonify(description='Latest transactions db doesn\'t exist. This is unusual, please report on https://github.com/ranchimall/ranchimallflo-api'), 500 if numberOfLatestBlocks is not None: c.execute('SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT {}) ORDER BY id ASC;'.format(int(numberOfLatestBlocks))) @@ -1894,7 +1864,7 @@ async def latestTransactionDetails(): tx_parsed_details['parsedFloData']['transactionType'] = item[4] tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) tempdict[json.loads(item[3])['txid']] = tx_parsed_details - return jsonify(result='ok', latestTransactions=tempdict) + return jsonify(latestTransactions=tempdict), 200 @app.route('/api/v1.0/getLatestBlockDetails', methods=['GET']) @@ -1929,13 +1899,12 @@ async def latestBlockDetails(): 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' + return jsonify(description='Latest transactions db doesn\'t exist. This is unusual, please report on https://github.com/ranchimall/ranchimallflo-api'), 404 if limit is None: c.execute('''SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT 4) ORDER BY id ASC;''') else: - int(limit) - c.execute('SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT {}) ORDER BY id ASC;'.format(limit)) + c.execute(f'SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT {limit}) ORDER BY id ASC;') latestBlocks = c.fetchall() c.close() tempdict = {} @@ -1979,9 +1948,9 @@ async def blocktransactions(blockdetail): "parsedFloData" : parseResult, "transactionDetails" : transactionJson } - return jsonify(result='ok', transactions=blocktxs, blockKeyword=blockdetail) + return jsonify(transactions=blocktxs, blockKeyword=blockdetail), 200 else: - return jsonify(result='error', description='Block doesn\'t exist in database') + return jsonify(description='Block doesn\'t exist in database'), 404 @app.route('/api/v1.0/categoriseString/') @@ -2021,11 +1990,11 @@ async def categoriseString_v2(urlstring): # check if the hash is of a transaction response = requests.get('{}tx/{}'.format(apiUrl, urlstring)) if response.status_code == 200: - return jsonify(type='transaction') + return jsonify(type='transaction'), 200 else: response = requests.get('{}block/{}'.format(apiUrl, urlstring)) if response.status_code == 200: - return jsonify(type='block') + return jsonify(type='block'), 200 else: # check urlstring is a token name tokenfolder = os.path.join(dbfolder, 'tokens') @@ -2033,19 +2002,18 @@ async def categoriseString_v2(urlstring): for f in os.listdir(tokenfolder) if os.path.isfile(os.path.join(tokenfolder, f))] if urlstring.lower() in onlyfiles: - return jsonify(type='token') + return jsonify(type='token'), 200 else: contractfolder = os.path.join(dbfolder, 'system.db') conn = sqlite3.connect(contractfolder) conn.row_factory = lambda cursor, row: row[0] c = conn.cursor() - contractList = c.execute( - 'select contractname from activeContracts').fetchall() + contractList = c.execute('select contractname from activeContracts').fetchall() if urlstring.lower() in contractList: - return jsonify(type='smartContract') + return jsonify(type='smartContract'), 200 else: - return jsonify(type='noise') + return jsonify(type='noise'), 200 @app.route('/api/v1.0/getTokenSmartContractList', methods=['GET']) @@ -2119,7 +2087,7 @@ async def tokenSmartContractList(): contractList.append(contractDict) - return jsonify(tokens=filelist, smartContracts=contractList, result='ok') + return jsonify(tokens=filelist, smartContracts=contractList), 200 class ServerSentEvent: @@ -2236,12 +2204,7 @@ async def priceData(): ratepair = list(ratepair) prices[ratepair[0]] = ratepair[1] - return jsonify(prices=prices, result='ok') - - -@app.route('/api/test') -async def test_msg(): - return jsonify('Welcome to Ranchi Mall API') + return jsonify(prices=prices), 200 # if system.db isn't present, initialize it From 2686bb70fc6f6f9999b49f54aad079d625d3debb Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Fri, 27 Jan 2023 07:37:43 +0000 Subject: [PATCH 06/46] Added senderAddress and receiverAddress along with the transactionDetails API --- ranchimallflo_api.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 4289400..aa9f31d 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -70,11 +70,9 @@ def blockdetailhelper(blockdetail): c = conn.cursor() if blockHash: - c.execute( - f"select jsonData from latestBlocks where blockHash='{blockHash}'") + c.execute(f"select jsonData from latestBlocks where blockHash='{blockHash}'") elif blockHeight: - c.execute( - f"select jsonData from latestBlocks where blockNumber='{blockHeight}'") + c.execute(f"select jsonData from latestBlocks where blockNumber='{blockHeight}'") blockJson = c.fetchall() return blockJson @@ -175,6 +173,15 @@ def fetchContractStatus(contractName, contractAddress): else: return status[0][0] +def extract_ip_op_addresses(transactionJson): + sender_address = transactionJson['vin'][0]['addr'] + receiver_address = None + for utxo in transactionJson['vout']: + if utxo['scriptPubKey']['addresses'][0] == sender_address: + continue + receiver_address = utxo['scriptPubKey']['addresses'][0] + return sender_address, receiver_address + @app.route('/') async def welcome_msg(): @@ -1717,6 +1724,7 @@ async def transactiondetails1(transactionHash): parseResult = json.loads(transactionJsonData[0][1]) operation = transactionJsonData[0][2] db_reference = transactionJsonData[0][3] + sender_address, receiver_address = extract_ip_op_addresses(transactionJson) operationDetails = {} @@ -1782,7 +1790,7 @@ async def transactiondetails1(transactionHash): if winningAmount[0][0] is not None: operationDetails['winningAmount'] = winningAmount[0][0] - return jsonify(parsedFloData=parseResult, transactionDetails=transactionJson, transactionHash=transactionHash, operation=operation, operationDetails=operationDetails), 200 + return jsonify(parsedFloData=parseResult, transactionDetails=transactionJson, transactionHash=transactionHash, operation=operation, operationDetails=operationDetails, senderAddress=sender_address, receiverAddress=receiver_address), 200 else: return jsonify(description='Transaction doesn\'t exist in database'), 404 From 3285e65a40eadca4df8fd7a8be53a1000f98e64c Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Sat, 4 Feb 2023 07:46:31 +0000 Subject: [PATCH 07/46] API input validation --- ranchimallflo_api.py | 1466 ++++++++++++++++++++++-------------------- 1 file changed, 758 insertions(+), 708 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index aa9f31d..1d5037e 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -8,7 +8,6 @@ import time from datetime import datetime from quart import jsonify, make_response, Quart, render_template, request, flash, redirect, url_for, send_file from quart_cors import cors - import asyncio from typing import Optional @@ -18,10 +17,7 @@ import subprocess from apscheduler.schedulers.background import BackgroundScheduler import atexit -import pathlib -import io -import zipfile -import tarfile +import pyflo import pdb @@ -29,8 +25,20 @@ app = Quart(__name__) app.clients = set() app = cors(app, allow_origin="*") +if net == 'mainnet': + is_testnet = False +elif net == 'testnet': + is_testnet = True -# helper functions + +# Validation functionss +def check_flo_address(floaddress, is_testnet=False): + return pyflo.is_address_valid(floaddress, testnet=is_testnet) + +def check_integer(value): + return str.isdigit(value) + +# Helper functions def retryRequest(tempserverlist, apicall): if len(tempserverlist) != 0: try: @@ -182,11 +190,43 @@ def extract_ip_op_addresses(transactionJson): receiver_address = utxo['scriptPubKey']['addresses'][0] return sender_address, receiver_address +def updatePrices(): + prices = {} + # USD -> INR + response = requests.get(f"https://api.exchangerate-api.com/v4/latest/usd") + price = response.json() + prices['USDINR'] = price['rates']['INR'] + + # Blockchain stuff : BTC,FLO -> USD,INR + # BTC->USD | BTC->INR + response = requests.get(f"https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,flo&vs_currencies=usd,inr") + price = response.json() + prices['BTCUSD'] = price['bitcoin']['usd'] + prices['BTCINR'] = price['bitcoin']['inr'] + + # FLO->USD | FLO->INR + response = requests.get(f"https://api.coinlore.net/api/ticker/?id=67") + price = response.json() + prices["FLOUSD"] = float(price[0]['price_usd']) + prices["FLOINR"] = float(prices["FLOUSD"]) * float(prices['USDINR']) + + # 3. update latest price data + print('Prices updated at time: %s' % datetime.now()) + print(prices) + + conn = sqlite3.connect('system.db') + c = conn.cursor() + for pair in list(prices.items()): + pair = list(pair) + c.execute(f"UPDATE ratepairs SET price={pair[1]} WHERE ratepair='{pair[0]}'") + conn.commit() + @app.route('/') async def welcome_msg(): return jsonify('Welcome to RanchiMall FLO Api v2') + @app.route('/api/v1.0/getSystemData', methods=['GET']) async def systemData(): # query for the number of flo addresses in tokenAddress mapping @@ -207,36 +247,12 @@ async def systemData(): return jsonify(systemAddressCount=tokenAddressCount, systemBlockCount=validatedBlockCount, systemTransactionCount=validatedTransactionCount, systemSmartContractCount=contractCount, systemTokenCount=tokenCount, lastscannedblock=lastscannedblock, result='ok') -@app.route('/api/v2/info', methods=['GET']) -async def info(): - # query for the number of flo addresses in tokenAddress mapping - conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) - c = conn.cursor() - tokenAddressCount = c.execute('select count(distinct tokenAddress) from tokenAddressMapping').fetchall()[0][0] - tokenCount = c.execute('select count(distinct token) from tokenAddressMapping').fetchall()[0][0] - contractCount = c.execute('select count(distinct contractName) from contractAddressMapping').fetchall()[0][0] - lastscannedblock = int(c.execute("select value from systemData where attribute=='lastblockscanned'").fetchall()[0][0]) - conn.close() - - # query for total number of validated blocks - conn = sqlite3.connect(os.path.join(dbfolder, 'latestCache.db')) - c = conn.cursor() - validatedBlockCount = c.execute('select count(distinct blockNumber) from latestBlocks').fetchall()[0][0] - validatedTransactionCount = c.execute('select count(distinct transactionHash) from latestTransactions').fetchall()[0][0] - conn.close() - - return jsonify(systemAddressCount=tokenAddressCount, systemBlockCount=validatedBlockCount, systemTransactionCount=validatedTransactionCount, systemSmartContractCount=contractCount, systemTokenCount=tokenCount, lastscannedblock=lastscannedblock), 200 @app.route('/api/v1.0/broadcastTx/') async def broadcastTx(raw_transaction_hash): p1 = subprocess.run(['flo-cli',f"-datadir={FLO_DATA_DIR}",'sendrawtransaction',raw_transaction_hash], capture_output=True) return jsonify(args=p1.args,returncode=p1.returncode,stdout=p1.stdout.decode(),stderr=p1.stderr.decode()) -@app.route('/api/v2/broadcastTx/') -async def broadcastTx_v2(raw_transaction_hash): - p1 = subprocess.run(['flo-cli',f"-datadir={FLO_DATA_DIR}",'sendrawtransaction',raw_transaction_hash], capture_output=True) - return jsonify(args=p1.args,returncode=p1.returncode,stdout=p1.stdout.decode(),stderr=p1.stderr.decode()), 200 - # FLO TOKEN APIs @app.route('/api/v1.0/getTokenList', methods=['GET']) @@ -249,16 +265,6 @@ async def getTokenList(): return jsonify(tokens=filelist, result='ok') -@app.route('/api/v2/tokenList', methods=['GET']) -async def tokenList(): - 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), 200 - - @app.route('/api/v1.0/getTokenInfo', methods=['GET']) async def getTokenInfo(): token = request.args.get('token') @@ -298,41 +304,6 @@ async def getTokenInfo(): activeAddress_no=numberOf_distinctAddresses, totalTransactions=numberOf_transactions, associatedSmartContracts=associatedContractList) -@app.route('/api/v2/tokenInfo/', methods=['GET']) -async def tokenInfo(token): - if token is None: - return jsonify(description='token name hasnt been passed'), 400 - - dblocation = dbfolder + '/tokens/' + str(token) + '.db' - if os.path.exists(dblocation): - conn = sqlite3.connect(dblocation) - c = conn.cursor() - else: - return jsonify(description='token doesn\'t exist'), 404 - 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] - c.execute('select max(id) from transactionHistory') - numberOf_transactions = c.fetchall()[0][0] - c.execute('select contractName, contractAddress, blockNumber, blockHash, transactionHash from tokenContractAssociation') - associatedContracts = c.fetchall() - conn.close() - - associatedContractList = [] - for item in associatedContracts: - tempdict = {} - item = list(item) - tempdict['contractName'] = item[0] - tempdict['contractAddress'] = item[1] - tempdict['blockNumber'] = item[2] - tempdict['blockHash'] = item[3] - tempdict['transactionHash'] = item[4] - associatedContractList.append(tempdict) - - return jsonify(token=token, incorporationAddress=incorporationRow[1], tokenSupply=incorporationRow[3], time=incorporationRow[6], blockchainReference=incorporationRow[7], activeAddress_no=numberOf_distinctAddresses, totalTransactions=numberOf_transactions, associatedSmartContracts=associatedContractList), 200 - - @app.route('/api/v1.0/getTokenTransactions', methods=['GET']) async def getTokenTransactions(): token = request.args.get('token') @@ -385,55 +356,6 @@ async def getTokenTransactions(): return jsonify(result='ok', token=token, transactions=rowarray_list) -@app.route('/api/v2/tokenTransactions/', methods=['GET']) -async def tokenTransactions(token): - if token is None: - return jsonify(description='token name hasnt been passed'), 400 - - senderFloAddress = request.args.get('senderFloAddress') - destFloAddress = request.args.get('destFloAddress') - limit = request.args.get('limit') - - 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 jsonify(description='token doesn\'t exist'), 404 - - if senderFloAddress and not destFloAddress: - if limit is None: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(senderFloAddress)) - else: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(senderFloAddress, limit)) - elif not senderFloAddress and destFloAddress: - if limit is None: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(destFloAddress)) - else: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(destFloAddress, limit)) - elif senderFloAddress and destFloAddress: - if limit is None: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(senderFloAddress, destFloAddress)) - else: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(senderFloAddress, destFloAddress, limit)) - - else: - if limit is None: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT 100') - else: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT {}'.format(limit)) - transactionJsonData = c.fetchall() - conn.close() - rowarray_list = {} - for row in transactionJsonData: - temp = {} - temp['transactionDetails'] = json.loads(row[0]) - temp['parsedFloData'] = json.loads(row[1]) - rowarray_list[temp['transactionDetails']['txid']] = temp - return jsonify(token=token, transactions=rowarray_list), 200 - - @app.route('/api/v1.0/getTokenBalances', methods=['GET']) async def getTokenBalances(): token = request.args.get('token') @@ -458,27 +380,6 @@ async def getTokenBalances(): return jsonify(result='ok', token=token, balances=returnList) -@app.route('/api/v2/tokenBalances/', methods=['GET']) -async def tokenBalances(token): - if token is None: - return jsonify(description='token name hasnt been passed'), 400 - - dblocation = dbfolder + '/tokens/' + str(token) + '.db' - if os.path.exists(dblocation): - conn = sqlite3.connect(dblocation) - c = conn.cursor() - else: - return jsonify(description='token doesn\'t exist'), 404 - c.execute('SELECT address,SUM(transferBalance) FROM activeTable GROUP BY address') - addressBalances = c.fetchall() - - returnList = {} - for address in addressBalances: - returnList[address[0]] = address[1] - - return jsonify(token=token, balances=returnList), 200 - - # FLO Address APIs @app.route('/api/v1.0/getFloAddressInfo', methods=['GET']) async def getFloAddressInfo(): @@ -539,56 +440,6 @@ async def getFloAddressInfo(): incorporatedSmartContracts=None) -@app.route('/api/v2/floAddressInfo/', methods=['GET']) -async def floAddressInfo(floAddress): - if floAddress is None: - return jsonify(description='floAddress hasn\'t been passed'), 400 - - dblocation = dbfolder + '/system.db' - if os.path.exists(dblocation): - conn = sqlite3.connect(dblocation) - c = conn.cursor() - c.execute(f'SELECT token FROM tokenAddressMapping WHERE tokenAddress="{floAddress}"') - tokenNames = c.fetchall() - c.execute(f"SELECT contractName, status, tokenIdentification, contractType, transactionHash, blockNumber, blockHash FROM activecontracts WHERE contractAddress='{floAddress}'") - incorporatedContracts = 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(f'SELECT SUM(transferBalance) FROM activeTable WHERE address="{floAddress}"') - balance = c.fetchall()[0][0] - tempdict['balance'] = balance - tempdict['token'] = token - detailList[token] = tempdict - else: - # Address is not associated with any token - return jsonify(description='FLO address is not associated with any tokens'), 404 - - if len(incorporatedContracts) != 0: - incorporatedSmartContracts = [] - for contract in incorporatedContracts: - tempdict = {} - tempdict['contractName'] = contract[0] - tempdict['contractAddress'] = floAddress - tempdict['status'] = contract[1] - tempdict['tokenIdentification'] = contract[2] - tempdict['contractType'] = contract[3] - tempdict['transactionHash'] = contract[4] - tempdict['blockNumber'] = contract[5] - tempdict['blockHash'] = contract[6] - incorporatedSmartContracts.append(tempdict) - else: - incorporatedContracts=None - return jsonify(floAddress=floAddress, floAddressBalances=detailList, incorporatedSmartContracts=None), 200 - - @app.route('/api/v1.0/getFloAddressBalance', methods=['GET']) async def getAddressBalance(): floAddress = request.args.get('floAddress') @@ -642,51 +493,6 @@ async def getAddressBalance(): return jsonify(result='ok', token=token, floAddress=floAddress, balance=balance) -@app.route('/api/v2/floAddressBalance/', methods=['GET']) -async def floAddressBalance(floAddress): - if floAddress is None: - return jsonify(description='floAddress hasn\'t been passed'), 400 - - token = request.args.get('token') - if token is None: - dblocation = dbfolder + '/system.db' - if os.path.exists(dblocation): - conn = sqlite3.connect(dblocation) - c = conn.cursor() - c.execute(f'SELECT token FROM tokenAddressMapping WHERE tokenAddress="{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(f'SELECT SUM(transferBalance) FROM activeTable WHERE address="{floAddress}"') - balance = c.fetchall()[0][0] - tempdict['balance'] = balance - tempdict['token'] = token - detailList[token] = tempdict - return jsonify(floAddress=floAddress, floAddressBalances=detailList), 200 - else: - # Address is not associated with any token - return jsonify(floAddress=floAddress, floAddressBalances={}), 200 - else: - dblocation = dbfolder + '/tokens/' + str(token) + '.db' - if os.path.exists(dblocation): - conn = sqlite3.connect(dblocation) - c = conn.cursor() - else: - return jsonify(description="token doesn't exist"), 404 - c.execute(f'SELECT SUM(transferBalance) FROM activeTable WHERE address="{floAddress}"') - balance = c.fetchall()[0][0] - conn.close() - return jsonify(token=token, floAddress=floAddress, balance=balance), 200 - - @app.route('/api/v1.0/getFloAddressTransactions', methods=['GET']) async def getFloAddressTransactions(): floAddress = request.args.get('floAddress') @@ -744,58 +550,6 @@ async def getFloAddressTransactions(): return jsonify(result='error', description='No token transactions present present on this address') -@app.route('/api/v2/floAddressTransactions/', methods=['GET']) -async def floAddressTransactions(floAddress): - if floAddress is None: - return jsonify(description='floAddress has not been passed'), 400 - - token = request.args.get('token') - limit = request.args.get('limit') - if token is None: - 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() - else: - dblocation = dbfolder + '/tokens/' + str(token) + '.db' - if os.path.exists(dblocation): - tokenNames = [[str(token), ]] - else: - return jsonify(description='token doesn\'t exist'), 404 - - if len(tokenNames) != 0: - allTransactionList = {} - - for tokenname in tokenNames: - tokenname = tokenname[0] - dblocation = dbfolder + '/tokens/' + str(tokenname) + '.db' - if os.path.exists(dblocation): - tempdict = {} - conn = sqlite3.connect(dblocation) - c = conn.cursor() - if limit is None: - c.execute(f'SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{floAddress}" OR destFloAddress="{floAddress}" ORDER BY id DESC LIMIT 100') - else: - c.execute(f'SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{floAddress}" OR destFloAddress="{floAddress}" ORDER BY id DESC LIMIT {limit}') - transactionJsonData = c.fetchall() - conn.close() - - for row in transactionJsonData: - temp = {} - temp['transactionDetails'] = json.loads(row[0]) - temp['parsedFloData'] = json.loads(row[1]) - allTransactionList[temp['transactionDetails']['txid']] = temp - - if token is None: - return jsonify(floAddress=floAddress, transactions=allTransactionList), 200 - else: - return jsonify(floAddress=floAddress, transactions=allTransactionList, token=token), 200 - else: - return jsonify(floAddress=floAddress, transactions={}, token=token), 200 - - # SMART CONTRACT APIs @app.route('/api/v1.0/getSmartContractList', methods=['GET']) async def getContractList(): @@ -890,32 +644,6 @@ async def getContractList(): return jsonify(smartContracts=contractList, result='ok') -@app.route('/api/v2/smartContractList', methods=['GET']) -async def getContractList_v2(): - contractName = request.args.get('contractName') - contractAddress = request.args.get('contractAddress') - # todo - Add validation for contractAddress and contractName to prevent SQL injection attacks - contractList = [] - conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) - c = conn.cursor() - smart_contracts = return_smart_contracts(c, contractName, contractAddress) - for idx, contract in enumerate(smart_contracts): - contractDict = {} - contractDict['contractName'] = contract[1] - contractDict['contractAddress'] = contract[2] - contractDict['status'] = contract[3] - contractDict['tokenIdentification'] = contract[4] - contractDict['contractType'] = contract[5] - contractDict['transactionHash'] = contract[6] - contractDict['blockNumber'] = contract[7] - contractDict['incorporationDate'] = contract[8] - contractDict['expiryDate'] = contract[9] - contractDict['closeDate'] = contract[10] - contractList.append(contractDict) - conn.close() - return jsonify(smartContracts=contractList, result='ok') - - @app.route('/api/v1.0/getSmartContractInfo', methods=['GET']) async def getContractInfo(): contractName = request.args.get('contractName') @@ -1017,55 +745,6 @@ async def getContractInfo(): return jsonify(result='error', details='Smart Contract with the given name doesn\'t exist') -@app.route('/api/v2/smartContractInfo', methods=['GET']) -async def getContractInfo_v2(): - contractName = request.args.get('contractName') - contractAddress = request.args.get('contractAddress') - if contractName is None: - return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 - if contractAddress is None: - return jsonify(description='Smart Contract\'s address hasn\'t been passed'), 400 - - contractName = contractName.strip() - contractAddress = contractAddress.strip() - contractStructure = fetchContractStructure(contractName, contractAddress) - if contractStructure: - returnval = contractStructure - # Categorize into what type of contract it is right now - if contractStructure['contractType'] == 'continuos-event' and contractStructure['subtype'] == 'tokenswap': - conn, c = create_database_connection('smart_contract', {'contract_name': contractName, 'contract_address': contractAddress}) - c.execute('select count(participantAddress), sum(tokenAmount), sum(winningAmount) from contractparticipants') - participation_details = c.fetchall() - returnval['numberOfParticipants'] = participation_details[0][0] - returnval['totalParticipationAmount'] = participation_details[0][1] - returnval['totalHonorAmount'] = participation_details[0][2] - c.execute('SELECT COUNT(DISTINCT transactionHash) FROM contractdeposits') - returnval['numberOfDeposits'] = c.fetchall()[0][0] - - elif contractStructure['contractType'] == 'one-time-event' and 'exitconditions' in contractStructure.keys(): - returnval['userChoice'] = contractStructure['exitconditions'] - returnval.pop('exitconditions') - conn, c = create_database_connection('system_dbs') - 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] - - elif contractStructure['contractType'] == 'one-time-event' and 'payeeAddress' in contractStructure.keys(): - pass - - return jsonify(contractName=contractName, contractAddress=contractAddress, contractInfo=returnval), 200 - else: - return jsonify(details='Smart Contract with the given name doesn\'t exist'), 404 - - @app.route('/api/v1.0/getSmartContractParticipants', methods=['GET']) async def getcontractparticipants(): contractName = request.args.get('contractName') @@ -1147,74 +826,6 @@ async def getcontractparticipants(): return jsonify(result='error', description='Smart Contract with the given name doesn\'t exist') -@app.route('/api/v2/smartContractParticipants', methods=['GET']) -async def getcontractparticipants_v2(): - contractName = request.args.get('contractName') - contractAddress = request.args.get('contractAddress') - - if contractName is None: - return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 - if contractAddress is None: - return jsonify(description='Smart Contract\'s address hasn\'t been passed'), 400 - - contractName = contractName.strip() - contractAddress = contractAddress.strip() - filelocation = os.path.join(dbfolder, 'smartContracts', '{}-{}.db'.format(contractName, contractAddress)) - - if os.path.isfile(filelocation): - # Make db connection and fetch data - contractStructure = fetchContractStructure(contractName, contractAddress) - contractStatus = fetchContractStatus(contractName, contractAddress) - conn, c = create_database_connection('smart_contract', {'contract_name': contractName, 'contract_address': contractAddress}) - if 'exitconditions' in contractStructure: - # contract is of the type external trigger - # check if the contract has been closed - if contractStatus == 'closed': - token = contractStructure['tokenIdentification'] - c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash, winningAmount FROM contractparticipants') - result = c.fetchall() - returnval = {} - for row in result: - returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4], 'winningAmount': row[5], 'tokenIdentification': token} - - else: - c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash FROM contractparticipants') - result = c.fetchall() - conn.close() - returnval = {} - for row in result: - returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4]} - - elif 'payeeAddress' in contractStructure: - # contract is of the type internal trigger - c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash FROM contractparticipants') - result = c.fetchall() - conn.close() - returnval = {} - for row in result: - returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'transactionHash': row[4]} - - elif contractStructure['contractType'] == 'continuos-event' and contractStructure['subtype'] == 'tokenswap': - c.execute('SELECT * FROM contractparticipants') - contract_participants = c.fetchall() - returnval = {} - for row in contract_participants: - returnval[row[1]] = { - 'participantFloAddress': row[1], - 'participationAmount': row[2], - 'swapPrice': float(row[3]), - 'transactionHash': row[4], - 'blockNumber': row[5], - 'blockHash': row[6], - 'swapAmount': row[7] - } - conn.close() - - return jsonify(contractName=contractName, contractAddress=contractAddress, participantInfo=returnval), 200 - else: - return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404 - - @app.route('/api/v1.0/getParticipantDetails', methods=['GET']) async def getParticipantDetails(): floAddress = request.args.get('floAddress') @@ -1416,19 +1027,703 @@ async def getParticipantDetails(): return jsonify(result='error', description='System error. System db is missing') +@app.route('/api/v1.0/getSmartContractTransactions', methods=['GET']) +async def getsmartcontracttransactions(): + contractName = request.args.get('contractName') + contractAddress = request.args.get('contractAddress') + + if contractName is None: + return jsonify(result='error', description='Smart Contract\'s name hasn\'t been passed') + + if contractAddress is None: + return jsonify(result='error', description='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 jsonData, parsedFloData from contractTransactionHistory') + result = c.fetchall() + conn.close() + returnval = {} + + for item in result: + temp = {} + temp['transactionDetails'] = json.loads(item[0]) + temp['parsedFloData'] = json.loads(item[1]) + returnval[temp['transactionDetails']['txid']] = temp + + return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, contractTransactions=returnval) + + else: + return jsonify(result='error', description='Smart Contract with the given name doesn\'t exist') + + +@app.route('/api/v1.0/getBlockDetails/', methods=['GET']) +async def getblockdetails(blockdetail): + blockJson = blockdetailhelper(blockdetail) + if len(blockJson) != 0: + blockJson = json.loads(blockJson[0][0]) + return jsonify(result='ok', blockDetails=blockJson) + else: + return jsonify(result='error', description='Block doesn\'t exist in database') + + +@app.route('/api/v1.0/getTransactionDetails/', methods=['GET']) +async def gettransactiondetails(transactionHash): + + transactionJsonData = transactiondetailhelper(transactionHash) + + if len(transactionJsonData) != 0: + transactionJson = json.loads(transactionJsonData[0][0]) + parseResult = json.loads(transactionJsonData[0][1]) + + return jsonify(parsedFloData=parseResult, transactionDetails=transactionJson, transactionHash=transactionHash, result='ok') + else: + return jsonify(result='error', description='Transaction doesn\'t exist in database') + + +@app.route('/api/v1.0/getLatestTransactionDetails', methods=['GET']) +async def getLatestTransactionDetails(): + numberOfLatestBlocks = request.args.get('numberOfLatestBlocks') + + 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' + + if numberOfLatestBlocks is not None: + c.execute('SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT {}) ORDER BY id ASC;'.format(int(numberOfLatestBlocks))) + 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[3]) + tx_parsed_details['parsedFloData'] = json.loads(item[5]) + tx_parsed_details['parsedFloData']['transactionType'] = item[4] + tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) + tempdict[json.loads(item[3])['txid']] = tx_parsed_details + else: + c.execute('''SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT 100) 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[3]) + tx_parsed_details['parsedFloData'] = json.loads(item[5]) + tx_parsed_details['parsedFloData']['transactionType'] = item[4] + tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) + tempdict[json.loads(item[3])['txid']] = tx_parsed_details + return jsonify(result='ok', latestTransactions=tempdict) + + +@app.route('/api/v1.0/getLatestBlockDetails', methods=['GET']) +async def getLatestBlockDetails(): + limit = request.args.get('limit') + + 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' + + if limit is None: + c.execute('''SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT 4) ORDER BY id ASC;''') + else: + int(limit) + c.execute('SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT {}) ORDER BY id ASC;'.format(limit)) + latestBlocks = c.fetchall() + c.close() + tempdict = {} + for idx, item in enumerate(latestBlocks): + tempdict[json.loads(item[3])['hash']] = json.loads(item[3]) + return jsonify(result='ok', latestBlocks=tempdict) + + +@app.route('/api/v1.0/getBlockTransactions/', methods=['GET']) +async def getblocktransactions(blockdetail): + blockJson = blockdetailhelper(blockdetail) + if len(blockJson) != 0: + blockJson = json.loads(blockJson[0][0]) + blocktxlist = blockJson['tx'] + blocktxs = {} + for i in range(len(blocktxlist)): + temptx = transactiondetailhelper(blocktxlist[i]) + transactionJson = json.loads(temptx[0][0]) + parseResult = json.loads(temptx[0][1]) + blocktxs[blocktxlist[i]] = { + "parsedFloData" : parseResult, + "transactionDetails" : transactionJson + } + return jsonify(result='ok', transactions=blocktxs, blockKeyword=blockdetail) + else: + return jsonify(result='error', description='Block doesn\'t exist in database') + + +@app.route('/api/v1.0/categoriseString/') +async def categoriseString(urlstring): + # check if the hash is of a transaction + response = requests.get('{}tx/{}'.format(apiUrl, urlstring)) + if response.status_code == 200: + return jsonify(type='transaction') + else: + response = requests.get('{}block/{}'.format(apiUrl, urlstring)) + if response.status_code == 200: + return jsonify(type='block') + else: + # check urlstring is a token name + tokenfolder = os.path.join(dbfolder, 'tokens') + onlyfiles = [f[:-3] + for f in os.listdir(tokenfolder) if os.path.isfile(os.path.join(tokenfolder, f))] + + if urlstring.lower() in onlyfiles: + return jsonify(type='token') + else: + contractfolder = os.path.join(dbfolder, 'system.db') + conn = sqlite3.connect(contractfolder) + conn.row_factory = lambda cursor, row: row[0] + c = conn.cursor() + contractList = c.execute( + 'select contractname from activeContracts').fetchall() + + if urlstring.lower() in contractList: + return jsonify(type='smartContract') + else: + return jsonify(type='noise') + + +@app.route('/api/v1.0/getTokenSmartContractList', methods=['GET']) +async def getTokenSmartContractList(): + # list of tokens + 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]) + + # list of smart contracts + conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) + c = conn.cursor() + + contractList = [] + + 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['tokenIdentification'] = contract[4] + contractDict['contractType'] = contract[5] + contractDict['transactionHash'] = contract[6] + contractDict['blockNumber'] = contract[7] + contractDict['blockHash'] = contract[8] + contractDict['incorporationDate'] = contract[9] + if contract[10]: + contractDict['expiryDate'] = contract[10] + if contract[11]: + contractDict['closeDate'] = contract[11] + + contractList.append(contractDict) + + return jsonify(tokens=filelist, smartContracts=contractList, result='ok') + + +################### +### VERSION 2 ### +################### + +@app.route('/api/v2/info', methods=['GET']) +async def info(): + # query for the number of flo addresses in tokenAddress mapping + conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) + c = conn.cursor() + tokenAddressCount = c.execute('select count(distinct tokenAddress) from tokenAddressMapping').fetchall()[0][0] + tokenCount = c.execute('select count(distinct token) from tokenAddressMapping').fetchall()[0][0] + contractCount = c.execute('select count(distinct contractName) from contractAddressMapping').fetchall()[0][0] + lastscannedblock = int(c.execute("select value from systemData where attribute=='lastblockscanned'").fetchall()[0][0]) + conn.close() + + # query for total number of validated blocks + conn = sqlite3.connect(os.path.join(dbfolder, 'latestCache.db')) + c = conn.cursor() + validatedBlockCount = c.execute('select count(distinct blockNumber) from latestBlocks').fetchall()[0][0] + validatedTransactionCount = c.execute('select count(distinct transactionHash) from latestTransactions').fetchall()[0][0] + conn.close() + + return jsonify(systemAddressCount=tokenAddressCount, systemBlockCount=validatedBlockCount, systemTransactionCount=validatedTransactionCount, systemSmartContractCount=contractCount, systemTokenCount=tokenCount, lastscannedblock=lastscannedblock), 200 + + +@app.route('/api/v2/broadcastTx/') +async def broadcastTx_v2(raw_transaction_hash): + p1 = subprocess.run(['flo-cli',f"-datadir={FLO_DATA_DIR}",'sendrawtransaction',raw_transaction_hash], capture_output=True) + return jsonify(args=p1.args,returncode=p1.returncode,stdout=p1.stdout.decode(),stderr=p1.stderr.decode()), 200 + + +# FLO TOKEN APIs +@app.route('/api/v2/tokenList', methods=['GET']) +async def tokenList(): + 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), 200 + + +@app.route('/api/v2/tokenInfo/', methods=['GET']) +async def tokenInfo(token): + if token is None: + return jsonify(description='token name hasnt been passed'), 400 + + # todo : input validation + + dblocation = dbfolder + '/tokens/' + str(token) + '.db' + if os.path.exists(dblocation): + conn = sqlite3.connect(dblocation) + c = conn.cursor() + else: + return jsonify(description='token doesn\'t exist'), 404 + 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] + c.execute('select max(id) from transactionHistory') + numberOf_transactions = c.fetchall()[0][0] + c.execute('select contractName, contractAddress, blockNumber, blockHash, transactionHash from tokenContractAssociation') + associatedContracts = c.fetchall() + conn.close() + + associatedContractList = [] + for item in associatedContracts: + tempdict = {} + item = list(item) + tempdict['contractName'] = item[0] + tempdict['contractAddress'] = item[1] + tempdict['blockNumber'] = item[2] + tempdict['blockHash'] = item[3] + tempdict['transactionHash'] = item[4] + associatedContractList.append(tempdict) + + return jsonify(token=token, incorporationAddress=incorporationRow[1], tokenSupply=incorporationRow[3], time=incorporationRow[6], blockchainReference=incorporationRow[7], activeAddress_no=numberOf_distinctAddresses, totalTransactions=numberOf_transactions, associatedSmartContracts=associatedContractList), 200 + + +@app.route('/api/v2/tokenTransactions/', methods=['GET']) +async def tokenTransactions(token): + if token is None: + return jsonify(description='token name hasnt been passed'), 400 + + # Input validations + senderFloAddress = request.args.get('senderFloAddress') + if senderFloAddress is not None and not check_flo_address(senderFloAddress, is_testnet): + return jsonify(description='senderFloAddress validation failed'), 400 + destFloAddress = request.args.get('destFloAddress') + if destFloAddress is not None and not check_flo_address(destFloAddress, is_testnet): + return jsonify(description='destFloAddress validation failed'), 400 + limit = request.args.get('limit') + if limit is not None and not check_integer(limit): + return jsonify(description='limit validation failed'), 400 + + 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 jsonify(description='token doesn\'t exist'), 404 + + if senderFloAddress and not destFloAddress: + if limit is None: + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(senderFloAddress)) + else: + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(senderFloAddress, limit)) + elif not senderFloAddress and destFloAddress: + if limit is None: + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(destFloAddress)) + else: + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(destFloAddress, limit)) + elif senderFloAddress and destFloAddress: + if limit is None: + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(senderFloAddress, destFloAddress)) + else: + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(senderFloAddress, destFloAddress, limit)) + + else: + if limit is None: + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT 100') + else: + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT {}'.format(limit)) + transactionJsonData = c.fetchall() + conn.close() + rowarray_list = {} + for row in transactionJsonData: + temp = {} + temp['transactionDetails'] = json.loads(row[0]) + temp['parsedFloData'] = json.loads(row[1]) + rowarray_list[temp['transactionDetails']['txid']] = temp + return jsonify(token=token, transactions=rowarray_list), 200 + + +@app.route('/api/v2/tokenBalances/', methods=['GET']) +async def tokenBalances(token): + if token is None: + return jsonify(description='token name hasnt been passed'), 400 + + dblocation = dbfolder + '/tokens/' + str(token) + '.db' + if os.path.exists(dblocation): + conn = sqlite3.connect(dblocation) + c = conn.cursor() + else: + return jsonify(description='token doesn\'t exist'), 404 + c.execute('SELECT address,SUM(transferBalance) FROM activeTable GROUP BY address') + addressBalances = c.fetchall() + + returnList = {} + for address in addressBalances: + returnList[address[0]] = address[1] + + return jsonify(token=token, balances=returnList), 200 + + +# FLO Address APIs +@app.route('/api/v2/floAddressInfo/', methods=['GET']) +async def floAddressInfo(floAddress): + if floAddress is None: + return jsonify(description='floAddress hasn\'t been passed'), 400 + # input validation + if not check_flo_address(floAddress, is_testnet): + return jsonify(description='floAddress validation failed'), 400 + + dblocation = dbfolder + '/system.db' + if os.path.exists(dblocation): + conn = sqlite3.connect(dblocation) + c = conn.cursor() + c.execute(f'SELECT token FROM tokenAddressMapping WHERE tokenAddress="{floAddress}"') + tokenNames = c.fetchall() + c.execute(f"SELECT contractName, status, tokenIdentification, contractType, transactionHash, blockNumber, blockHash FROM activecontracts WHERE contractAddress='{floAddress}'") + incorporatedContracts = 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(f'SELECT SUM(transferBalance) FROM activeTable WHERE address="{floAddress}"') + balance = c.fetchall()[0][0] + tempdict['balance'] = balance + tempdict['token'] = token + detailList[token] = tempdict + else: + # Address is not associated with any token + return jsonify(description='FLO address is not associated with any tokens'), 404 + + if len(incorporatedContracts) != 0: + incorporatedSmartContracts = [] + for contract in incorporatedContracts: + tempdict = {} + tempdict['contractName'] = contract[0] + tempdict['contractAddress'] = floAddress + tempdict['status'] = contract[1] + tempdict['tokenIdentification'] = contract[2] + tempdict['contractType'] = contract[3] + tempdict['transactionHash'] = contract[4] + tempdict['blockNumber'] = contract[5] + tempdict['blockHash'] = contract[6] + incorporatedSmartContracts.append(tempdict) + else: + incorporatedContracts=None + return jsonify(floAddress=floAddress, floAddressBalances=detailList, incorporatedSmartContracts=None), 200 + + +@app.route('/api/v2/floAddressBalance/', methods=['GET']) +async def floAddressBalance(floAddress): + if floAddress is None: + return jsonify(description='floAddress hasn\'t been passed'), 400 + # input validation + if not check_flo_address(floAddress, is_testnet): + return jsonify(description='floAddress validation failed'), 400 + + token = request.args.get('token') + if token is None: + dblocation = dbfolder + '/system.db' + if os.path.exists(dblocation): + conn = sqlite3.connect(dblocation) + c = conn.cursor() + c.execute(f'SELECT token FROM tokenAddressMapping WHERE tokenAddress="{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(f'SELECT SUM(transferBalance) FROM activeTable WHERE address="{floAddress}"') + balance = c.fetchall()[0][0] + tempdict['balance'] = balance + tempdict['token'] = token + detailList[token] = tempdict + return jsonify(floAddress=floAddress, floAddressBalances=detailList), 200 + else: + # Address is not associated with any token + return jsonify(floAddress=floAddress, floAddressBalances={}), 200 + else: + dblocation = dbfolder + '/tokens/' + str(token) + '.db' + if os.path.exists(dblocation): + conn = sqlite3.connect(dblocation) + c = conn.cursor() + else: + return jsonify(description="token doesn't exist"), 404 + c.execute(f'SELECT SUM(transferBalance) FROM activeTable WHERE address="{floAddress}"') + balance = c.fetchall()[0][0] + conn.close() + return jsonify(token=token, floAddress=floAddress, balance=balance), 200 + + +@app.route('/api/v2/floAddressTransactions/', methods=['GET']) +async def floAddressTransactions(floAddress): + if floAddress is None: + return jsonify(description='floAddress has not been passed'), 400 + if not check_flo_address(floAddress, is_testnet): + return jsonify(description='floAddress validation failed'), 400 + limit = request.args.get('limit') + if limit is not None and not check_integer(limit): + return jsonify(description='limit validation failed'), 400 + + token = request.args.get('token') + if token is None: + 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() + else: + dblocation = dbfolder + '/tokens/' + str(token) + '.db' + if os.path.exists(dblocation): + tokenNames = [[str(token), ]] + else: + return jsonify(description='token doesn\'t exist'), 404 + + if len(tokenNames) != 0: + allTransactionList = {} + + for tokenname in tokenNames: + tokenname = tokenname[0] + dblocation = dbfolder + '/tokens/' + str(tokenname) + '.db' + if os.path.exists(dblocation): + tempdict = {} + conn = sqlite3.connect(dblocation) + c = conn.cursor() + if limit is None: + c.execute(f'SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{floAddress}" OR destFloAddress="{floAddress}" ORDER BY id DESC LIMIT 100') + else: + c.execute(f'SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{floAddress}" OR destFloAddress="{floAddress}" ORDER BY id DESC LIMIT {limit}') + transactionJsonData = c.fetchall() + conn.close() + + for row in transactionJsonData: + temp = {} + temp['transactionDetails'] = json.loads(row[0]) + temp['parsedFloData'] = json.loads(row[1]) + allTransactionList[temp['transactionDetails']['txid']] = temp + + if token is None: + return jsonify(floAddress=floAddress, transactions=allTransactionList), 200 + else: + return jsonify(floAddress=floAddress, transactions=allTransactionList, token=token), 200 + else: + return jsonify(floAddress=floAddress, transactions={}, token=token), 200 + + +# SMART CONTRACT APIs +@app.route('/api/v2/smartContractList', methods=['GET']) +async def getContractList_v2(): + contractName = request.args.get('contractName') + contractAddress = request.args.get('contractAddress') + # todo - Add validation for contractAddress and contractName to prevent SQL injection attacks + if contractAddress is not None and not check_flo_address(contractAddress, is_testnet): + return jsonify(description='contractAddress validation failed'), 400 + + contractList = [] + conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) + c = conn.cursor() + smart_contracts = return_smart_contracts(c, contractName, contractAddress) + for idx, contract in enumerate(smart_contracts): + contractDict = {} + contractDict['contractName'] = contract[1] + contractDict['contractAddress'] = contract[2] + contractDict['status'] = contract[3] + contractDict['tokenIdentification'] = contract[4] + contractDict['contractType'] = contract[5] + contractDict['transactionHash'] = contract[6] + contractDict['blockNumber'] = contract[7] + contractDict['incorporationDate'] = contract[8] + contractDict['expiryDate'] = contract[9] + contractDict['closeDate'] = contract[10] + contractList.append(contractDict) + conn.close() + return jsonify(smartContracts=contractList, result='ok') + + +@app.route('/api/v2/smartContractInfo', methods=['GET']) +async def getContractInfo_v2(): + contractName = request.args.get('contractName') + contractAddress = request.args.get('contractAddress') + + if contractName is None: + return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 + contractName = contractName.strip() + + if contractAddress is None: + return jsonify(description='Smart Contract\'s address hasn\'t been passed'), 400 + contractAddress = contractAddress.strip() + if not check_flo_address(contractAddress, is_testnet): + return jsonify(description='contractAddress validation failed'), 400 + + contractStructure = fetchContractStructure(contractName, contractAddress) + if contractStructure: + returnval = contractStructure + # Categorize into what type of contract it is right now + if contractStructure['contractType'] == 'continuos-event' and contractStructure['subtype'] == 'tokenswap': + conn, c = create_database_connection('smart_contract', {'contract_name': contractName, 'contract_address': contractAddress}) + c.execute('select count(participantAddress), sum(tokenAmount), sum(winningAmount) from contractparticipants') + participation_details = c.fetchall() + returnval['numberOfParticipants'] = participation_details[0][0] + returnval['totalParticipationAmount'] = participation_details[0][1] + returnval['totalHonorAmount'] = participation_details[0][2] + c.execute('SELECT COUNT(DISTINCT transactionHash) FROM contractdeposits') + returnval['numberOfDeposits'] = c.fetchall()[0][0] + + elif contractStructure['contractType'] == 'one-time-event' and 'exitconditions' in contractStructure.keys(): + returnval['userChoice'] = contractStructure['exitconditions'] + returnval.pop('exitconditions') + conn, c = create_database_connection('system_dbs') + 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] + + elif contractStructure['contractType'] == 'one-time-event' and 'payeeAddress' in contractStructure.keys(): + pass + + return jsonify(contractName=contractName, contractAddress=contractAddress, contractInfo=returnval), 200 + else: + return jsonify(details='Smart Contract with the given name doesn\'t exist'), 404 + + +@app.route('/api/v2/smartContractParticipants', methods=['GET']) +async def getcontractparticipants_v2(): + contractName = request.args.get('contractName') + contractName = contractName.strip() + if contractName is None: + return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 + + contractAddress = request.args.get('contractAddress') + contractAddress = contractAddress.strip() + if contractAddress is None: + return jsonify(description='Smart Contract\'s address hasn\'t been passed'), 400 + if not check_flo_address(contractAddress, is_testnet): + return jsonify(description='contractAddress validation failed'), 400 + + filelocation = os.path.join(dbfolder, 'smartContracts', '{}-{}.db'.format(contractName, contractAddress)) + if os.path.isfile(filelocation): + # Make db connection and fetch data + contractStructure = fetchContractStructure(contractName, contractAddress) + contractStatus = fetchContractStatus(contractName, contractAddress) + conn, c = create_database_connection('smart_contract', {'contract_name': contractName, 'contract_address': contractAddress}) + if 'exitconditions' in contractStructure: + # contract is of the type external trigger + # check if the contract has been closed + if contractStatus == 'closed': + token = contractStructure['tokenIdentification'] + c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash, winningAmount FROM contractparticipants') + result = c.fetchall() + returnval = {} + for row in result: + returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4], 'winningAmount': row[5], 'tokenIdentification': token} + + else: + c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash FROM contractparticipants') + result = c.fetchall() + conn.close() + returnval = {} + for row in result: + returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4]} + + elif 'payeeAddress' in contractStructure: + # contract is of the type internal trigger + c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash FROM contractparticipants') + result = c.fetchall() + conn.close() + returnval = {} + for row in result: + returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'transactionHash': row[4]} + + elif contractStructure['contractType'] == 'continuos-event' and contractStructure['subtype'] == 'tokenswap': + c.execute('SELECT * FROM contractparticipants') + contract_participants = c.fetchall() + returnval = {} + for row in contract_participants: + returnval[row[1]] = { + 'participantFloAddress': row[1], + 'participationAmount': row[2], + 'swapPrice': float(row[3]), + 'transactionHash': row[4], + 'blockNumber': row[5], + 'blockHash': row[6], + 'swapAmount': row[7] + } + conn.close() + + return jsonify(contractName=contractName, contractAddress=contractAddress, participantInfo=returnval), 200 + else: + return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404 + + @app.route('/api/v2/participantDetails/', methods=['GET']) async def participantDetails(): if floAddress is None: return jsonify(description='FLO address hasn\'t been passed'), 400 + if not check_flo_address(floAddress, is_testnet): + return jsonify(description='floAddress validation failed'), 400 + contractName = request.args.get('contractName') + contractName = contractName.strip() + contractAddress = request.args.get('contractAddress') - - dblocation = os.path.join(dbfolder, 'system.db') + contractAddress = contractAddress.strip() + if contractAddress is not None and not check_flo_address(contractAddress, is_testnet): + return jsonify(description='contractAddress validation failed'), 400 if (contractName and contractAddress is None) or (contractName is None and contractAddress): return jsonify(description='pass both, contractName and contractAddress as url parameters'), 400 - #if os.path.isfile(dblocation) and os.path.isfile(contract_db): + dblocation = os.path.join(dbfolder, 'system.db') if os.path.isfile(dblocation): # Make db connection and fetch data conn = sqlite3.connect(dblocation) @@ -1615,49 +1910,19 @@ async def participantDetails(): return jsonify(description='System error. System db is missing'), 404 -@app.route('/api/v1.0/getSmartContractTransactions', methods=['GET']) -async def getsmartcontracttransactions(): - contractName = request.args.get('contractName') - contractAddress = request.args.get('contractAddress') - - if contractName is None: - return jsonify(result='error', description='Smart Contract\'s name hasn\'t been passed') - - if contractAddress is None: - return jsonify(result='error', description='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 jsonData, parsedFloData from contractTransactionHistory') - result = c.fetchall() - conn.close() - returnval = {} - - for item in result: - temp = {} - temp['transactionDetails'] = json.loads(item[0]) - temp['parsedFloData'] = json.loads(item[1]) - returnval[temp['transactionDetails']['txid']] = temp - - return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, contractTransactions=returnval) - - else: - return jsonify(result='error', description='Smart Contract with the given name doesn\'t exist') - - @app.route('/api/v2/smartContractTransactions', methods=['GET']) async def smartcontracttransactions(): contractName = request.args.get('contractName') - contractAddress = request.args.get('contractAddress') + contractName = contractName.strip() if contractName is None: return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 + + contractAddress = request.args.get('contractAddress') + contractAddress = contractAddress.strip() if contractAddress is None: return jsonify(description='Smart Contract\'s address hasn\'t been passed'), 400 + if not check_flo_address(contractAddress, is_testnet): + return jsonify(description='contractAddress validation failed'), 400 contractDbName = '{}-{}.db'.format(contractName.strip(), contractAddress.strip()) filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) @@ -1682,19 +1947,11 @@ async def smartcontracttransactions(): return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404 -@app.route('/api/v1.0/getBlockDetails/', methods=['GET']) -async def getblockdetails(blockdetail): - blockJson = blockdetailhelper(blockdetail) - if len(blockJson) != 0: - blockJson = json.loads(blockJson[0][0]) - return jsonify(result='ok', blockDetails=blockJson) - else: - return jsonify(result='error', description='Block doesn\'t exist in database') +@app.route('/api/v2/blockDetails/', methods=['GET']) +async def blockdetails(blockHash): + # todo - validate blockHash - -@app.route('/api/v2/blockDetails/', methods=['GET']) -async def blockdetails(blockdetail): - blockJson = blockdetailhelper(blockdetail) + blockJson = blockdetailhelper(blockHash) if len(blockJson) != 0: blockJson = json.loads(blockJson[0][0]) return jsonify(blockDetails=blockJson), 200 @@ -1702,22 +1959,10 @@ async def blockdetails(blockdetail): return jsonify(description='Block doesn\'t exist in database'), 404 -@app.route('/api/v1.0/getTransactionDetails/', methods=['GET']) -async def gettransactiondetails(transactionHash): - - transactionJsonData = transactiondetailhelper(transactionHash) - - if len(transactionJsonData) != 0: - transactionJson = json.loads(transactionJsonData[0][0]) - parseResult = json.loads(transactionJsonData[0][1]) - - return jsonify(parsedFloData=parseResult, transactionDetails=transactionJson, transactionHash=transactionHash, result='ok') - else: - return jsonify(result='error', description='Transaction doesn\'t exist in database') - - @app.route('/api/v2/transactionDetails/', methods=['GET']) async def transactiondetails1(transactionHash): + # todo - validate transactionHash + transactionJsonData = transactiondetailhelper(transactionHash) if len(transactionJsonData) != 0: transactionJson = json.loads(transactionJsonData[0][0]) @@ -1795,49 +2040,11 @@ async def transactiondetails1(transactionHash): return jsonify(description='Transaction doesn\'t exist in database'), 404 -@app.route('/api/v1.0/getLatestTransactionDetails', methods=['GET']) -async def getLatestTransactionDetails(): - numberOfLatestBlocks = request.args.get('numberOfLatestBlocks') - - 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' - - if numberOfLatestBlocks is not None: - c.execute('SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT {}) ORDER BY id ASC;'.format(int(numberOfLatestBlocks))) - 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[3]) - tx_parsed_details['parsedFloData'] = json.loads(item[5]) - tx_parsed_details['parsedFloData']['transactionType'] = item[4] - tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) - tempdict[json.loads(item[3])['txid']] = tx_parsed_details - else: - c.execute('''SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT 100) 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[3]) - tx_parsed_details['parsedFloData'] = json.loads(item[5]) - tx_parsed_details['parsedFloData']['transactionType'] = item[4] - tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) - tempdict[json.loads(item[3])['txid']] = tx_parsed_details - return jsonify(result='ok', latestTransactions=tempdict) - - @app.route('/api/v2/latestTransactionDetails', methods=['GET']) async def latestTransactionDetails(): numberOfLatestBlocks = request.args.get('numberOfLatestBlocks') + if numberOfLatestBlocks is not None and not check_integer(numberOfLatestBlocks): + return jsonify(description='numberOfLatestBlocks validation failed'), 400 dblocation = dbfolder + '/latestCache.db' if os.path.exists(dblocation): @@ -1875,33 +2082,12 @@ async def latestTransactionDetails(): return jsonify(latestTransactions=tempdict), 200 -@app.route('/api/v1.0/getLatestBlockDetails', methods=['GET']) -async def getLatestBlockDetails(): - limit = request.args.get('limit') - - 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' - - if limit is None: - c.execute('''SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT 4) ORDER BY id ASC;''') - else: - int(limit) - c.execute('SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT {}) ORDER BY id ASC;'.format(limit)) - latestBlocks = c.fetchall() - c.close() - tempdict = {} - for idx, item in enumerate(latestBlocks): - tempdict[json.loads(item[3])['hash']] = json.loads(item[3]) - return jsonify(result='ok', latestBlocks=tempdict) - - @app.route('/api/v2/latestBlockDetails', methods=['GET']) async def latestBlockDetails(): limit = request.args.get('limit') + if limit is not None and not check_integer(limit): + return jsonify(description='limit validation failed'), 400 + dblocation = dbfolder + '/latestCache.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) @@ -1921,9 +2107,9 @@ async def latestBlockDetails(): return jsonify(result='ok', latestBlocks=tempdict) -@app.route('/api/v1.0/getBlockTransactions/', methods=['GET']) -async def getblocktransactions(blockdetail): - blockJson = blockdetailhelper(blockdetail) +@app.route('/api/v2/blockTransactions/', methods=['GET']) +async def blocktransactions(blockHash): + blockJson = blockdetailhelper(blockHash) if len(blockJson) != 0: blockJson = json.loads(blockJson[0][0]) blocktxlist = blockJson['tx'] @@ -1936,63 +2122,11 @@ async def getblocktransactions(blockdetail): "parsedFloData" : parseResult, "transactionDetails" : transactionJson } - return jsonify(result='ok', transactions=blocktxs, blockKeyword=blockdetail) - else: - return jsonify(result='error', description='Block doesn\'t exist in database') - - -@app.route('/api/v2/blockTransactions/', methods=['GET']) -async def blocktransactions(blockdetail): - blockJson = blockdetailhelper(blockdetail) - if len(blockJson) != 0: - blockJson = json.loads(blockJson[0][0]) - blocktxlist = blockJson['tx'] - blocktxs = {} - for i in range(len(blocktxlist)): - temptx = transactiondetailhelper(blocktxlist[i]) - transactionJson = json.loads(temptx[0][0]) - parseResult = json.loads(temptx[0][1]) - blocktxs[blocktxlist[i]] = { - "parsedFloData" : parseResult, - "transactionDetails" : transactionJson - } - return jsonify(transactions=blocktxs, blockKeyword=blockdetail), 200 + return jsonify(transactions=blocktxs, blockKeyword=blockHash), 200 else: return jsonify(description='Block doesn\'t exist in database'), 404 -@app.route('/api/v1.0/categoriseString/') -async def categoriseString(urlstring): - # check if the hash is of a transaction - response = requests.get('{}tx/{}'.format(apiUrl, urlstring)) - if response.status_code == 200: - return jsonify(type='transaction') - else: - response = requests.get('{}block/{}'.format(apiUrl, urlstring)) - if response.status_code == 200: - return jsonify(type='block') - else: - # check urlstring is a token name - tokenfolder = os.path.join(dbfolder, 'tokens') - onlyfiles = [f[:-3] - for f in os.listdir(tokenfolder) if os.path.isfile(os.path.join(tokenfolder, f))] - - if urlstring.lower() in onlyfiles: - return jsonify(type='token') - else: - contractfolder = os.path.join(dbfolder, 'system.db') - conn = sqlite3.connect(contractfolder) - conn.row_factory = lambda cursor, row: row[0] - c = conn.cursor() - contractList = c.execute( - 'select contractname from activeContracts').fetchall() - - if urlstring.lower() in contractList: - return jsonify(type='smartContract') - else: - return jsonify(type='noise') - - @app.route('/api/v2/categoriseString/') async def categoriseString_v2(urlstring): # check if the hash is of a transaction @@ -2024,43 +2158,6 @@ async def categoriseString_v2(urlstring): return jsonify(type='noise'), 200 -@app.route('/api/v1.0/getTokenSmartContractList', methods=['GET']) -async def getTokenSmartContractList(): - # list of tokens - 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]) - - # list of smart contracts - conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) - c = conn.cursor() - - contractList = [] - - 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['tokenIdentification'] = contract[4] - contractDict['contractType'] = contract[5] - contractDict['transactionHash'] = contract[6] - contractDict['blockNumber'] = contract[7] - contractDict['blockHash'] = contract[8] - contractDict['incorporationDate'] = contract[9] - if contract[10]: - contractDict['expiryDate'] = contract[10] - if contract[11]: - contractDict['closeDate'] = contract[11] - - contractList.append(contractDict) - - return jsonify(tokens=filelist, smartContracts=contractList, result='ok') - - @app.route('/api/v2/tokenSmartContractList', methods=['GET']) async def tokenSmartContractList(): # list of tokens @@ -2099,7 +2196,6 @@ async def tokenSmartContractList(): class ServerSentEvent: - def __init__( self, data: str, @@ -2124,7 +2220,6 @@ class ServerSentEvent: message = f"{message}\r\n\r\n" return message.encode('utf-8') - @app.route('/sse') async def sse(): queue = asyncio.Queue() @@ -2150,55 +2245,6 @@ async def sse(): response.timeout = None return response - -''' Stuff required for getPrices endpoint ''' -def updatePrices(): - prices = {} - # USD -> INR - response = requests.get(f"https://api.exchangerate-api.com/v4/latest/usd") - price = response.json() - prices['USDINR'] = price['rates']['INR'] - - # Blockchain stuff : BTC,FLO -> USD,INR - # BTC->USD | BTC->INR - response = requests.get(f"https://api.coingecko.com/api/v3/simple/price?ids=bitcoin,flo&vs_currencies=usd,inr") - price = response.json() - prices['BTCUSD'] = price['bitcoin']['usd'] - prices['BTCINR'] = price['bitcoin']['inr'] - - # FLO->USD | FLO->INR - response = requests.get(f"https://api.coinlore.net/api/ticker/?id=67") - price = response.json() - prices["FLOUSD"] = float(price[0]['price_usd']) - prices["FLOINR"] = float(prices["FLOUSD"]) * float(prices['USDINR']) - - # 3. update latest price data - print('Prices updated at time: %s' % datetime.now()) - print(prices) - - conn = sqlite3.connect('system.db') - c = conn.cursor() - for pair in list(prices.items()): - pair = list(pair) - c.execute(f"UPDATE ratepairs SET price={pair[1]} WHERE ratepair='{pair[0]}'") - conn.commit() - -@app.route('/api/v1.0/getPrices', methods=['GET']) -async def getPriceData(): - # read system.db for price data - conn = sqlite3.connect('system.db') - c = conn.cursor() - ratepairs = c.execute('select ratepair, price from ratepairs') - ratepairs = ratepairs.fetchall() - prices = {} - - for ratepair in ratepairs: - ratepair = list(ratepair) - prices[ratepair[0]] = ratepair[1] - - return jsonify(prices=prices, result='ok') - - @app.route('/api/v2/prices', methods=['GET']) async def priceData(): # read system.db for price data @@ -2215,6 +2261,10 @@ async def priceData(): return jsonify(prices=prices), 200 +####################### +####################### + + # if system.db isn't present, initialize it if not os.path.isfile(f"system.db"): # create an empty db From 130662a6a276bd751729fbe5a437ef274a8213b3 Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Mon, 6 Feb 2023 18:11:21 +0000 Subject: [PATCH 08/46] Fixed order of transactions in API responses --- ranchimallflo_api.py | 193 +++++++++++++++++++++---------------------- 1 file changed, 92 insertions(+), 101 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 1d5037e..e21b209 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -10,14 +10,13 @@ from quart import jsonify, make_response, Quart, render_template, request, flash from quart_cors import cors import asyncio from typing import Optional - from config import * import parsing import subprocess - from apscheduler.schedulers.background import BackgroundScheduler import atexit import pyflo +from operator import itemgetter import pdb @@ -324,24 +323,24 @@ async def getTokenTransactions(): if senderFloAddress and not destFloAddress: if limit is None: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(senderFloAddress)) + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC'.format(senderFloAddress)) else: c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(senderFloAddress, limit)) elif not senderFloAddress and destFloAddress: if limit is None: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(destFloAddress)) + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC'.format(destFloAddress)) else: c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(destFloAddress, limit)) elif senderFloAddress and destFloAddress: if limit is None: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(senderFloAddress, destFloAddress)) + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC'.format(senderFloAddress, destFloAddress)) else: c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(senderFloAddress, destFloAddress, limit)) else: if limit is None: c.execute( - 'SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT 100') + 'SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC') else: c.execute( 'SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT {}'.format(limit)) @@ -527,11 +526,9 @@ async def getFloAddressTransactions(): conn = sqlite3.connect(dblocation) c = conn.cursor() if limit is None: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" OR destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format( - floAddress, floAddress)) + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" OR destFloAddress="{}" ORDER BY id DESC'.format(floAddress, floAddress)) else: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" OR destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format( - floAddress, floAddress, limit)) + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" OR destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(floAddress, floAddress, limit)) transactionJsonData = c.fetchall() conn.close() @@ -726,8 +723,7 @@ async def getContractInfo(): # this is a normal trigger # find the winning condition - c.execute( - 'select userchoice from contractparticipants where winningAmount is not null limit 1') + c.execute('select userchoice from contractparticipants where winningAmount is not null limit 1') returnval['winningChoice'] = c.fetchall()[0][0] # find the winning participants c.execute( @@ -1111,7 +1107,7 @@ async def getLatestTransactionDetails(): tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) tempdict[json.loads(item[3])['txid']] = tx_parsed_details else: - c.execute('''SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT 100) ORDER BY id ASC;''') + c.execute('''SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC) ORDER BY id ASC;''') latestTransactions = c.fetchall() c.close() tempdict = {} @@ -1248,17 +1244,17 @@ async def info(): # query for the number of flo addresses in tokenAddress mapping conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) c = conn.cursor() - tokenAddressCount = c.execute('select count(distinct tokenAddress) from tokenAddressMapping').fetchall()[0][0] - tokenCount = c.execute('select count(distinct token) from tokenAddressMapping').fetchall()[0][0] - contractCount = c.execute('select count(distinct contractName) from contractAddressMapping').fetchall()[0][0] - lastscannedblock = int(c.execute("select value from systemData where attribute=='lastblockscanned'").fetchall()[0][0]) + tokenAddressCount = c.execute('SELECT COUNT(distinct tokenAddress) FROM tokenAddressMapping').fetchall()[0][0] + tokenCount = c.execute('SELECT COUNT(distinct token) FROM tokenAddressMapping').fetchall()[0][0] + contractCount = c.execute('SELECT COUNT(distinct contractName) FROM contractAddressMapping').fetchall()[0][0] + lastscannedblock = int(c.execute("SELECT value FROM systemData WHERE attribute=='lastblockscanned'").fetchall()[0][0]) conn.close() # query for total number of validated blocks conn = sqlite3.connect(os.path.join(dbfolder, 'latestCache.db')) c = conn.cursor() - validatedBlockCount = c.execute('select count(distinct blockNumber) from latestBlocks').fetchall()[0][0] - validatedTransactionCount = c.execute('select count(distinct transactionHash) from latestTransactions').fetchall()[0][0] + validatedBlockCount = c.execute('SELECT COUNT(distinct blockNumber) FROM latestBlocks').fetchall()[0][0] + validatedTransactionCount = c.execute('SELECT COUNT(distinct transactionHash) FROM latestTransactions').fetchall()[0][0] conn.close() return jsonify(systemAddressCount=tokenAddressCount, systemBlockCount=validatedBlockCount, systemTransactionCount=validatedTransactionCount, systemSmartContractCount=contractCount, systemTokenCount=tokenCount, lastscannedblock=lastscannedblock), 200 @@ -1284,7 +1280,7 @@ async def tokenList(): @app.route('/api/v2/tokenInfo/', methods=['GET']) async def tokenInfo(token): if token is None: - return jsonify(description='token name hasnt been passed'), 400 + return jsonify(description='Token name hasnt been passed'), 400 # todo : input validation @@ -1293,14 +1289,14 @@ async def tokenInfo(token): conn = sqlite3.connect(dblocation) c = conn.cursor() else: - return jsonify(description='token doesn\'t exist'), 404 + return jsonify(description="Token doesn't exist"), 404 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] - c.execute('select max(id) from transactionHistory') + c.execute('SELECT MAX(id) FROM transactionHistory') numberOf_transactions = c.fetchall()[0][0] - c.execute('select contractName, contractAddress, blockNumber, blockHash, transactionHash from tokenContractAssociation') + c.execute('SELECT contractName, contractAddress, blockNumber, blockHash, transactionHash FROM tokenContractAssociation') associatedContracts = c.fetchall() conn.close() @@ -1321,7 +1317,7 @@ async def tokenInfo(token): @app.route('/api/v2/tokenTransactions/', methods=['GET']) async def tokenTransactions(token): if token is None: - return jsonify(description='token name hasnt been passed'), 400 + return jsonify(description='Token name hasnt been passed'), 400 # Input validations senderFloAddress = request.args.get('senderFloAddress') @@ -1340,51 +1336,52 @@ async def tokenTransactions(token): conn.row_factory = sqlite3.Row c = conn.cursor() else: - return jsonify(description='token doesn\'t exist'), 404 + return jsonify(description="Token doesn't exist"), 404 if senderFloAddress and not destFloAddress: if limit is None: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(senderFloAddress)) + c.execute('SELECT jsonData, parsedFloData, blocktime FROM transactionHistory WHERE sourceFloAddress="{}"'.format(senderFloAddress)) else: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(senderFloAddress, limit)) + c.execute('SELECT jsonData, parsedFloData, blocktime FROM transactionHistory WHERE sourceFloAddress="{}" LIMIT {}'.format(senderFloAddress, limit)) elif not senderFloAddress and destFloAddress: if limit is None: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(destFloAddress)) + c.execute('SELECT jsonData, parsedFloData, blocktime FROM transactionHistory WHERE destFloAddress="{}"'.format(destFloAddress)) else: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(destFloAddress, limit)) + c.execute('SELECT jsonData, parsedFloData, blocktime FROM transactionHistory WHERE destFloAddress="{}" LIMIT {}'.format(destFloAddress, limit)) elif senderFloAddress and destFloAddress: if limit is None: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC LIMIT 100'.format(senderFloAddress, destFloAddress)) + c.execute('SELECT jsonData, parsedFloData, blocktime FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}"'.format(senderFloAddress, destFloAddress)) else: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" ORDER BY id DESC LIMIT {}'.format(senderFloAddress, destFloAddress, limit)) - + c.execute('SELECT jsonData, parsedFloData, blocktime FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" LIMIT {}'.format(senderFloAddress, destFloAddress, limit)) else: if limit is None: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT 100') + c.execute('SELECT jsonData, parsedFloData, blocktime FROM transactionHistory') else: - c.execute('SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT {}'.format(limit)) + c.execute('SELECT jsonData, parsedFloData, blocktime FROM transactionHistory LIMIT {}'.format(limit)) transactionJsonData = c.fetchall() conn.close() - rowarray_list = {} - for row in transactionJsonData: + + rowarray_list = [] + sorted_list = sorted(transactionJsonData, key=itemgetter('blocktime'), reverse=True) + for row in sorted_list: temp = {} temp['transactionDetails'] = json.loads(row[0]) temp['parsedFloData'] = json.loads(row[1]) - rowarray_list[temp['transactionDetails']['txid']] = temp + rowarray_list.append(temp) return jsonify(token=token, transactions=rowarray_list), 200 @app.route('/api/v2/tokenBalances/', methods=['GET']) async def tokenBalances(token): if token is None: - return jsonify(description='token name hasnt been passed'), 400 + return jsonify(description='Token name hasnt been passed'), 400 dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() else: - return jsonify(description='token doesn\'t exist'), 404 + return jsonify(description="Token doesn't exist"), 404 c.execute('SELECT address,SUM(transferBalance) FROM activeTable GROUP BY address') addressBalances = c.fetchall() @@ -1490,7 +1487,7 @@ async def floAddressBalance(floAddress): conn = sqlite3.connect(dblocation) c = conn.cursor() else: - return jsonify(description="token doesn't exist"), 404 + return jsonify(description="Token doesn't exist"), 404 c.execute(f'SELECT SUM(transferBalance) FROM activeTable WHERE address="{floAddress}"') balance = c.fetchall()[0][0] conn.close() @@ -1513,44 +1510,45 @@ async def floAddressTransactions(floAddress): if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() - c.execute('select token from tokenAddressMapping where tokenAddress="{}"'.format(floAddress)) + c.execute('SELECT token FROM tokenAddressMapping WHERE tokenAddress="{}"'.format(floAddress)) tokenNames = c.fetchall() else: dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): tokenNames = [[str(token), ]] else: - return jsonify(description='token doesn\'t exist'), 404 + return jsonify(description="Token doesn't exist"), 404 if len(tokenNames) != 0: - allTransactionList = {} - + allTransactionList = [] for tokenname in tokenNames: tokenname = tokenname[0] dblocation = dbfolder + '/tokens/' + str(tokenname) + '.db' if os.path.exists(dblocation): - tempdict = {} conn = sqlite3.connect(dblocation) + conn.row_factory = sqlite3.Row c = conn.cursor() if limit is None: - c.execute(f'SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{floAddress}" OR destFloAddress="{floAddress}" ORDER BY id DESC LIMIT 100') + c.execute(f'SELECT jsonData, parsedFloData, blocktime FROM transactionHistory WHERE sourceFloAddress="{floAddress}" OR destFloAddress="{floAddress}" ORDER BY blocktime DESC') else: - c.execute(f'SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{floAddress}" OR destFloAddress="{floAddress}" ORDER BY id DESC LIMIT {limit}') + c.execute(f'SELECT jsonData, parsedFloData, blocktime FROM transactionHistory WHERE sourceFloAddress="{floAddress}" OR destFloAddress="{floAddress}" ORDER BY blocktime DESC LIMIT {limit}') transactionJsonData = c.fetchall() conn.close() - - for row in transactionJsonData: - temp = {} - temp['transactionDetails'] = json.loads(row[0]) - temp['parsedFloData'] = json.loads(row[1]) - allTransactionList[temp['transactionDetails']['txid']] = temp - + allTransactionList = allTransactionList + transactionJsonData + + rowarray_list = [] + allTransactionList = sorted(allTransactionList, key=itemgetter('blocktime'), reverse=True) + for row in allTransactionList: + tx = {} + tx['transactionDetails'] = json.loads(row[0]) + tx['parsedFloData'] = json.loads(row[1]) + rowarray_list.append(tx) if token is None: - return jsonify(floAddress=floAddress, transactions=allTransactionList), 200 + return jsonify(floAddress=floAddress, transactions=rowarray_list), 200 else: - return jsonify(floAddress=floAddress, transactions=allTransactionList, token=token), 200 + return jsonify(floAddress=floAddress, transactions=rowarray_list, token=token), 200 else: - return jsonify(floAddress=floAddress, transactions={}, token=token), 200 + return jsonify(floAddress=floAddress, transactions=[], token=token), 200 # SMART CONTRACT APIs @@ -1580,7 +1578,7 @@ async def getContractList_v2(): contractDict['closeDate'] = contract[10] contractList.append(contractDict) conn.close() - return jsonify(smartContracts=contractList, result='ok') + return jsonify(smartContracts=contractList), 200 @app.route('/api/v2/smartContractInfo', methods=['GET']) @@ -1604,7 +1602,7 @@ async def getContractInfo_v2(): # Categorize into what type of contract it is right now if contractStructure['contractType'] == 'continuos-event' and contractStructure['subtype'] == 'tokenswap': conn, c = create_database_connection('smart_contract', {'contract_name': contractName, 'contract_address': contractAddress}) - c.execute('select count(participantAddress), sum(tokenAmount), sum(winningAmount) from contractparticipants') + c.execute('SELECT COUNT(participantAddress), SUM(tokenAmount), SUM(winningAmount) FROM contractparticipants') participation_details = c.fetchall() returnval['numberOfParticipants'] = participation_details[0][0] returnval['totalParticipationAmount'] = participation_details[0][1] @@ -1616,7 +1614,7 @@ async def getContractInfo_v2(): returnval['userChoice'] = contractStructure['exitconditions'] returnval.pop('exitconditions') conn, c = create_database_connection('system_dbs') - c.execute('select status, incorporationDate, expiryDate, closeDate from activecontracts where contractName=="{}" and contractAddress=="{}"'.format(contractName.strip(), contractAddress.strip())) + 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: @@ -1633,7 +1631,7 @@ async def getContractInfo_v2(): return jsonify(contractName=contractName, contractAddress=contractAddress, contractInfo=returnval), 200 else: - return jsonify(details='Smart Contract with the given name doesn\'t exist'), 404 + return jsonify(details="Smart Contract with the given name doesn't exist"), 404 @app.route('/api/v2/smartContractParticipants', methods=['GET']) @@ -1795,7 +1793,7 @@ async def participantDetails(): detailsDict['tokenAmount'] = contract[5] detailsDict['transactionHash'] = contract[6] - c.execute(f"select status, tokenIdentification, contractType, blockNumber, blockHash, incorporationDate, expiryDate, closeDate from activecontracts where contractName='{detailsDict['contractName']}' and contractAddress='{detailsDict['contractAddress']}'") + c.execute(f"SELECT status, tokenIdentification, contractType, blockNumber, blockHash, incorporationDate, expiryDate, closeDate FROM activecontracts WHERE contractName='{detailsDict['contractName']}' AND contractAddress='{detailsDict['contractAddress']}'") temp = c.fetchall() detailsDict['status'] = temp[0][0] detailsDict['tokenIdentification'] = temp[0][1] @@ -1833,7 +1831,7 @@ async def participantDetails(): if 'payeeAddress' in contractStructure: # contract is of the type external trigger # check if the contract has been closed - c.execute(f"SELECT tokenAmount FROM contractparticipants where participantAddress='{floAddress}'") + c.execute(f"SELECT tokenAmount FROM contractparticipants WHERE participantAddress='{floAddress}'") result = c.fetchall() conn.close() detailsDict['tokenAmount'] = result[0][0] @@ -1848,7 +1846,7 @@ async def participantDetails(): detailsDict['tokenAmount'] = contract[5] detailsDict['transactionHash'] = contract[6] - c.execute(f"select status, tokenIdentification, contractType, blockNumber, blockHash, incorporationDate, expiryDate, closeDate from activecontracts where contractName='{detailsDict['contractName']}' and contractAddress='{detailsDict['contractAddress']}'") + c.execute(f"SELECT status, tokenIdentification, contractType, blockNumber, blockHash, incorporationDate, expiryDate, closeDate FROM activecontracts WHERE contractName='{detailsDict['contractName']}' AND contractAddress='{detailsDict['contractAddress']}'") temp = c.fetchall() detailsDict['status'] = temp[0][0] detailsDict['tokenIdentification'] = temp[0][1] @@ -1886,28 +1884,27 @@ async def participantDetails(): if 'exitconditions' in contractStructure: # contract is of the type external trigger # check if the contract has been closed - c.execute('select * from contractTransactionHistory where transactionType="trigger"') + c.execute('SELECT * FROM contractTransactionHistory WHERE transactionType="trigger"') trigger = c.fetchall() if detailsDict['status'] == 'closed': - c.execute(f"SELECT userChoice, winningAmount FROM contractparticipants where participantAddress='{floAddress}'") + c.execute(f"SELECT userChoice, winningAmount FROM contractparticipants WHERE participantAddress='{floAddress}'") result = c.fetchall() conn.close() detailsDict['userChoice'] = result[0][0] detailsDict['winningAmount'] = result[0][1] else: - c.execute(f"SELECT userChoice FROM contractparticipants where participantAddress='{floAddress}'") + c.execute(f"SELECT userChoice FROM contractparticipants WHERE participantAddress='{floAddress}'") result = c.fetchall() conn.close() detailsDict['userChoice'] = result[0][0] - participationDetailsList.append(detailsDict) return jsonify(floAddress=floAddress, type='participant', participatedContracts=participationDetailsList), 200 else: return jsonify(description="Address hasn't participated in any other contract"), 404 else: - return jsonify(description='System error. System db is missing'), 404 + return jsonify(description='System error. System.db is missing. This is unusual, please report on https://github.com/ranchimall/ranchimallflo-api'), 500 @app.route('/api/v2/smartContractTransactions', methods=['GET']) @@ -1924,6 +1921,10 @@ async def smartcontracttransactions(): if not check_flo_address(contractAddress, is_testnet): return jsonify(description='contractAddress validation failed'), 400 + limit = request.args.get('limit') + if limit is not None and not check_integer(limit): + return jsonify(description='limit validation failed'), 400 + contractDbName = '{}-{}.db'.format(contractName.strip(), contractAddress.strip()) filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) @@ -1931,16 +1932,18 @@ async def smartcontracttransactions(): # Make db connection and fetch data conn = sqlite3.connect(filelocation) c = conn.cursor() - c.execute('select jsonData, parsedFloData from contractTransactionHistory') + if limit is None: + c.execute('SELECT jsonData, parsedFloData FROM contractTransactionHistory ORDER BY blocktime DESC') + else: + c.execute(f'SELECT jsonData, parsedFloData FROM contractTransactionHistory ORDER BY blocktime DESC LIMIT {limit}') result = c.fetchall() conn.close() - returnval = {} - + returnval = [] for item in result: - temp = {} - temp['transactionDetails'] = json.loads(item[0]) - temp['parsedFloData'] = json.loads(item[1]) - returnval[temp['transactionDetails']['txid']] = temp + tx = {} + tx['transactionDetails'] = json.loads(item[0]) + tx['parsedFloData'] = json.loads(item[1]) + returnval.append(tx) return jsonify(contractName=contractName, contractAddress=contractAddress, contractTransactions=returnval), 200 else: @@ -1972,7 +1975,6 @@ async def transactiondetails1(transactionHash): sender_address, receiver_address = extract_ip_op_addresses(transactionJson) operationDetails = {} - if operation == 'smartContractDeposit': # open the db reference and check if there is a deposit return conn = sqlite3.connect(f"{dbfolder}/smartContracts/{db_reference}.db") @@ -2055,31 +2057,20 @@ async def latestTransactionDetails(): if numberOfLatestBlocks is not None: c.execute('SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT {}) ORDER BY id ASC;'.format(int(numberOfLatestBlocks))) - 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[3]) - tx_parsed_details['parsedFloData'] = json.loads(item[5]) - tx_parsed_details['parsedFloData']['transactionType'] = item[4] - tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) - tempdict[json.loads(item[3])['txid']] = tx_parsed_details else: - c.execute('''SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT 100) 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[3]) - tx_parsed_details['parsedFloData'] = json.loads(item[5]) - tx_parsed_details['parsedFloData']['transactionType'] = item[4] - tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) - tempdict[json.loads(item[3])['txid']] = tx_parsed_details - return jsonify(latestTransactions=tempdict), 200 + c.execute('''SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC) ORDER BY id ASC;''') + latestTransactions = c.fetchall() + c.close() + tx_list = [] + for idx, item in enumerate(latestTransactions): + item = list(item) + tx_parsed_details = {} + tx_parsed_details['transactionDetails'] = json.loads(item[3]) + tx_parsed_details['parsedFloData'] = json.loads(item[5]) + tx_parsed_details['parsedFloData']['transactionType'] = item[4] + tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) + tx_list.append(tx_parsed_details) + return jsonify(latestTransactions=tx_list), 200 @app.route('/api/v2/latestBlockDetails', methods=['GET']) From bc88536d9e13329ffd8b95a23f957323600e6bab Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Fri, 10 Feb 2023 16:27:29 +0000 Subject: [PATCH 09/46] Making smart contract name input to be case insensitive --- ranchimallflo_api.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index e21b209..77dd2ca 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -1273,7 +1273,6 @@ async def tokenList(): 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), 200 @@ -1283,7 +1282,6 @@ async def tokenInfo(token): return jsonify(description='Token name hasnt been passed'), 400 # todo : input validation - dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) @@ -1534,8 +1532,7 @@ async def floAddressTransactions(floAddress): c.execute(f'SELECT jsonData, parsedFloData, blocktime FROM transactionHistory WHERE sourceFloAddress="{floAddress}" OR destFloAddress="{floAddress}" ORDER BY blocktime DESC LIMIT {limit}') transactionJsonData = c.fetchall() conn.close() - allTransactionList = allTransactionList + transactionJsonData - + allTransactionList = allTransactionList + transactionJsonData rowarray_list = [] allTransactionList = sorted(allTransactionList, key=itemgetter('blocktime'), reverse=True) for row in allTransactionList: @@ -1555,6 +1552,7 @@ async def floAddressTransactions(floAddress): @app.route('/api/v2/smartContractList', methods=['GET']) async def getContractList_v2(): contractName = request.args.get('contractName') + contractName = contractName.strip().lower() contractAddress = request.args.get('contractAddress') # todo - Add validation for contractAddress and contractName to prevent SQL injection attacks if contractAddress is not None and not check_flo_address(contractAddress, is_testnet): @@ -1588,7 +1586,7 @@ async def getContractInfo_v2(): if contractName is None: return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 - contractName = contractName.strip() + contractName = contractName.strip().lower() if contractAddress is None: return jsonify(description='Smart Contract\'s address hasn\'t been passed'), 400 @@ -1637,9 +1635,9 @@ async def getContractInfo_v2(): @app.route('/api/v2/smartContractParticipants', methods=['GET']) async def getcontractparticipants_v2(): contractName = request.args.get('contractName') - contractName = contractName.strip() if contractName is None: return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 + contractName = contractName.strip().lower() contractAddress = request.args.get('contractAddress') contractAddress = contractAddress.strip() @@ -1711,7 +1709,7 @@ async def participantDetails(): return jsonify(description='floAddress validation failed'), 400 contractName = request.args.get('contractName') - contractName = contractName.strip() + contractName = contractName.strip().lower() contractAddress = request.args.get('contractAddress') contractAddress = contractAddress.strip() @@ -1910,7 +1908,7 @@ async def participantDetails(): @app.route('/api/v2/smartContractTransactions', methods=['GET']) async def smartcontracttransactions(): contractName = request.args.get('contractName') - contractName = contractName.strip() + contractName = contractName.strip().lower() if contractName is None: return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 From 0f5809acf8fac266d7cff8ab7408a439071701cc Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Fri, 3 Mar 2023 11:22:56 +0000 Subject: [PATCH 10/46] Updating parsing module and .gitignore --- .gitignore | 3 + parsing.py | 1437 +++++++++++++++++++++++++++++++++++++++++----------- 2 files changed, 1148 insertions(+), 292 deletions(-) diff --git a/.gitignore b/.gitignore index 7d86da5..184331f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ __pycache__/ config.py .idea/ py3.7/ +py3/ *.db +*.code-workspace +*.log diff --git a/parsing.py b/parsing.py index de65e03..9395ac3 100644 --- a/parsing.py +++ b/parsing.py @@ -1,170 +1,361 @@ +import pdb import re import arrow -import configparser +import pyflo +import logging +import json -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 ) -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 } +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}") -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 +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:['@','*','$',':','#','#'] + } +} -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 +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, + 'feb': 2, + 'mar': 3, + 'apr': 4, + 'may': 5, + 'jun': 6, + 'jul': 7, + 'aug': 8, + 'sep': 9, + 'oct': 10, + 'nov': 11, + 'dec': 12 +} -def isSmartContract(text): - textList = text.split(' ') - for word in textList: - if word == '': - continue - if word.endswith('@') and len(word) != 1: - return word - return False +# HELPER FUNCTIONS - -def isSmartContractPay(text): - wordlist = text.split(' ') - if len(wordlist) != 2: +# Find some value or return as noise +def apply_rule1(*argv): + a = argv[0](*argv[1:]) + if a is False: 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 a + + +def extract_substing_between(test_str, sub1, sub2): + # getting index of substrings + idx1 = test_str.index(sub1) + idx2 = test_str.index(sub2) + + # length of substring 1 is added to + # get string from next character + res = test_str[idx1 + len(sub1) + 1: idx2] + + # return result + return res + +# StateF functions +def isStateF(text): + try: + statef_string = extract_substing_between(text, 'statef', 'end-statef').strip() + i=iter(statef_string.split(":")) + statef_list = [":".join(x) for x in zip(i,i)] + statef = {} + for keyval in statef_list: + keyval = keyval.split(':') + statef[keyval[0]] = keyval[1] + return statef + except: 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 +# 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 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 +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 None + return d -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 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 + 'stateF': argv[4] + } + 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 + 'stateF': argv[4] + } + 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], + 'userchoices' : argv[8], + 'expiryTime' : argv[9] + }, + 'stateF': argv[10] + } + 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 + 'stateF': argv[7] + } + 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] + 'stateF': argv[3] + } + 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] + }, + 'stateF': argv[10] + } + 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], + }, + 'stateF': argv[10] + } + 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] + }, + 'stateF': argv[6] + } + 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], + 'stateF': argv[6] + } + return remove_empty_from_dict(parsed_data) + elif argv[0] == 'nft_create': + parsed_data = { + 'type': 'nftIncorporation', + 'flodata': argv[1], #string + 'tokenIdentification': argv[2], #hashList[0][:-1] + 'tokenAmount': argv[3], #initTokens, + 'nftHash': argv[4], #nftHash + 'stateF': argv[5] + } + return parsed_data + elif argv[0] == 'nft_transfer': + parsed_data = { + 'type': 'transfer', + 'transferType': 'nft', + 'flodata': argv[1], #string + 'tokenIdentification': argv[2], #hashList[0][:-1] + 'tokenAmount': argv[3], #initTokens, + 'stateF': argv[4] + } + return parsed_data + elif argv[0] == 'infinite_token_create': + parsed_data = { + 'type': 'infiniteTokenIncorporation', + 'flodata': argv[1], #string + 'tokenIdentification': argv[2], #hashList[0][:-1] + 'stateF': argv[3] + } + return parsed_data -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 extract_specialcharacter_words(rawstring, special_characters): + wordList = [] + for word in rawstring.split(' '): + if (len(word) not in [0,1] or word==":") and word[-1] in special_characters: + wordList.append(word) + return wordList -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) +def extract_contract_conditions(text, contract_type, marker=None, blocktime=None): + try: + rulestext = extract_substing_between(text, 'contract-conditions', 'end-contract-conditions') + except: + return False + if rulestext.strip()[0] == ':': + rulestext = rulestext.strip()[1:].strip() + #rulestext = re.split('contract-conditions:\s*', text)[-1] + # rulelist = re.split('\d\.\s*', rulestext) rulelist = [] numberList = re.findall(r'\(\d\d*\)', rulestext) @@ -174,214 +365,876 @@ def extractContractConditions(text, contracttype, marker, blocktime): 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 + logger.info('Contract condition numbers are not in order') + return False if idx == len(numberList) - 2: 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]) 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 + logger.info('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 + logger.info('Error parsing expiry time') + return False for rule in rulelist: - if rule=='': + if rule == '': continue elif rule[:14] == 'contractamount': - pattern = re.compile('[^contractamount\s*=\s*].*') - searchResult = pattern.search(rule).group(0) + pattern = re.compile('(^contractamount\s*=\s*)(.*)') + searchResult = pattern.search(rule).group(2) contractamount = searchResult.split(marker)[0] try: extractedRules['contractAmount'] = float(contractamount) except: - print("Contract amount entered is not a decimal") + logger.info("Contract amount entered is not a decimal") elif rule[:11] == 'userchoices': - pattern = re.compile('[^userchoices\s*=\s*].*') - conditions = pattern.search(rule).group(0) + pattern = re.compile('(^userchoices\s*=\s*)(.*)') + conditions = pattern.search(rule).group(2) 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) + pattern = re.compile('(^minimumsubscriptionamount\s*=\s*)(.*)') + searchResult = pattern.search(rule).group(2) minimumsubscriptionamount = searchResult.split(marker)[0] try: extractedRules['minimumsubscriptionamount'] = float(minimumsubscriptionamount) except: - print("Minimum subscription amount entered is not a decimal") + logger.info("Minimum subscription amount entered is not a decimal") elif rule[:25] == 'maximumsubscriptionamount': - pattern = re.compile('[^maximumsubscriptionamount\s*=\s*].*') - searchResult = pattern.search(rule).group(0) + pattern = re.compile('(^maximumsubscriptionamount\s*=\s*)(.*)') + searchResult = pattern.search(rule).group(2) maximumsubscriptionamount = searchResult.split(marker)[0] try: extractedRules['maximumsubscriptionamount'] = float(maximumsubscriptionamount) except: - print("Maximum subscription amount entered is not a decimal") + logger.info("Maximum subscription amount entered is not a decimal") elif rule[:12] == 'payeeaddress': - pattern = re.compile('[^payeeAddress\s*=\s*].*') - searchResult = pattern.search(rule).group(0) + pattern = re.compile('(^payeeaddress\s*=\s*)(.*)') + searchResult = pattern.search(rule).group(2) payeeAddress = searchResult.split(marker)[0] extractedRules['payeeAddress'] = payeeAddress - - if len(extractedRules)>1 and 'expiryTime' in extractedRules: + if len(extractedRules) > 1 and 'expiryTime' in extractedRules: return extractedRules else: - return None - return None + return False + + 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(2) + extractedRules['subtype'] = subtype + elif rule[:15] == 'accepting_token': + pattern = re.compile('(?<=accepting_token\s=\s)(.*)(? 1: + return extractedRules + else: + return False + return False -def extractTriggerCondition(text): +def extract_tokenswap_contract_conditions(processed_text, contract_type, contract_token): + rulestext = re.split('contract-conditions:\s*', processed_text)[-1] + 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]: + logger.info('Contract condition numbers are not in order') + return False + 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: + 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*)(.*)') + searchResult = pattern.search(rule).group(2) + subtype = searchResult.split(marker)[0] + #extractedRules['subtype'] = rule.split('=')[1].strip() + 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_deposit_conditions(text, blocktime=None): + 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]: + logger.info('Deposit condition numbers are not in order') + return False + 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: + logger.info('Expirytime of the contract is earlier than the block it is incorporated in. This incorporation will be rejected ') + return False + extractedRules['expiryTime'] = expirytime + except: + logger.info('Error parsing expiry time') + return False + + """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 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 extract_NFT_hash(clean_text): + nft_hash = re.search(r"(?:0[xX])?[0-9a-fA-F]{64}",clean_text) + if nft_hash is None: + return False + else: + return nft_hash.group(0) + + +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_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 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(-?[.\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 pyflo.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): - - # 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 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]: - 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'} +# 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 - # 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'} +def check_existence_of_keyword(inputlist, keywordlist): + for word in keywordlist: + if not word in inputlist: + return False + return True + + +def check_word_existence_instring(word, text): + word_exists = re.search(fr"\b{word}\b",text) + if word_exists is None: + return False else: - parsed_data = {'type': 'noise'} + return word_exists.group(0) - return parsed_data +send_category = ['transfer', 'send', 'give'] # keep everything lowercase +create_category = ['incorporate', 'create', 'start'] # keep everything lowercase +deposit_category = ['submit','deposit'] + + +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 + + +def selectCategory(rawstring, category1, category2): + foundCategory1 = None + foundCategory2 = None + + for word in category1: + if findWholeWord(word)(rawstring): + foundCategory1 = word + break + + 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' + + +def select_category_reject(rawstring, category1, category2, reject_list): + foundCategory1 = None + foundCategory2 = None + rejectCategory = None + + for word in category1: + if findWholeWord(word)(rawstring): + foundCategory1 = word + break + + for word in category2: + if findWholeWord(word)(rawstring): + foundCategory2 = word + break + + for word in reject_list: + if findWholeWord(word)(rawstring): + rejectCategory = word + break + + 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' + + +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() + + return clean_text,processed_text + + +# TODO - REMOVE SAMPLE TEXT +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 usd# as infinite-token', + 'transfer 10 usd#', + + 'Create 100 albumname# as NFT with 2CF24DBA5FB0A30E26E83B2AC5B9E29E1B161E5C1FA7425E73043362938B9824 as asset hash', + 'Transfer 10 albumname# nft', + + 'Create 400 rmt#', + 'Transfer 20 rmt#' +] + +text_list2 = [ + '''Create Smart Contract with the name swap-rupee-bioscope@ of the type continuous-event* + at the address stateF=bitcoin_price_source:bitpay:usd_inr_exchange_source:bitpay end-stateF oYzeeUBWRpzRuczW6myh2LHGnXPyR2Bc6k$ with contract-conditions : + (1) subtype = tokenswap + (2) accepting_token = rupee# + (3) selling_token = sreeram# + (4) price = "15" + (5) priceType="predetermined" end-contract-conditions''', + + ''' + Create a smart contract of the name simple-crowd-fund@ of the type one-time-event* using asset bioscope# at the FLO address oQkpZCBcAWc945viKqFmJVbVG4aKY4V3Gz$ with contract-conditions:(1) expiryTime= Tue Sep 13 2022 16:10:00 GMT+0530 (2) payeeAddress=oQotdnMBAP1wZ6Kiofx54S2jNjKGiFLYD7 end-contract-conditions + ''', + + ''' + Create a smart contract of the name simple-crowd-fund@ of the type one-time-event* using asset bioscope# at the FLO address oQkpZCBcAWc945viKqFmJVbVG4aKY4V3Gz$ with contract-conditions:(1) expiryTime= Tue Sep 13 2022 16:10:00 GMT+0530 (2) payeeAddress=oU412TvcMe2ah2xzqFpA95vBJ1RoPZY1LR:10:oVq6QTUeNLh8sapQ6J6EjMQMKHxFCt3uAq:20:oLE79kdHPEZ2bxa3PwtysbJeLo9hvPgizU:60:ocdCT9RAzWVsUncMu24r3HXKXFCXD7gTqh:10 end-contract-conditions + ''', + ''' + Create a smart contract of the name simple-crowd-fund@ of the type one-time-event* using asset bioscope# at the FLO address oQkpZCBcAWc945viKqFmJVbVG4aKY4V3Gz$ with contract-conditions:(1) expiryTime= Tue Sep 13 2022 16:10:00 GMT+0530 (2) payeeAddress=oU412TvcMe2ah2xzqFpA95vBJ1RoPZY1LR end-contract-conditions + ''', + ''' + Create a smart contract of the name all-crowd-fund-7@ of the type one-time-event* using asset bioscope# at the FLO address oYX4GvBYtfTBNyUFRCdtYubu7ZS4gchvrb$ with contract-conditions:(1) expiryTime= Sun Nov 15 2022 12:30:00 GMT+0530 (2) payeeAddress=oQotdnMBAP1wZ6Kiofx54S2jNjKGiFLYD7:10:oMunmikKvxsMSTYzShm2X5tGrYDt9EYPij:20:oRpvvGEVKwWiMnzZ528fPhiA2cZA3HgXY5:30:oWpVCjPDGzaiVfEFHs6QVM56V1uY1HyCJJ:40 (3) minimumsubscriptionamount=1 (5) contractAmount=0.6 end-contract-conditions + ''', + ''' + Create a smart contract of the name all-crowd-fund-7@ of the type one-time-event* using asset bioscope# at the FLO address oYX4GvBYtfTBNyUFRCdtYubu7ZS4gchvrb$ with contract-conditions:(1) expiryTime= Sun Nov 15 2022 12:30:00 GMT+0530 (2) payeeAddress=oQotdnMBAP1wZ6Kiofx54S2jNjKGiFLYD7:0:oMunmikKvxsMSTYzShm2X5tGrYDt9EYPij:30:oRpvvGEVKwWiMnzZ528fPhiA2cZA3HgXY5:30:oWpVCjPDGzaiVfEFHs6QVM56V1uY1HyCJJ:40 (3) minimumsubscriptionamount=1 (4) contractAmount=0.6 end-contract-conditions + ''', + '''send 0.02 bioscope# to twitter-survive@ to FLO address oVbebBNuERWbouDg65zLfdataWEMTnsL8r with the userchoice: survives''', + ''' + Create a smart contract of the name twitter-survive@ of the type one-time-event* using asset bioscope# at the FLO address oVbebBNuERWbouDg65zLfdataWEMTnsL8r$ with contract-conditions:(1) expiryTime= Sun Nov 15 2022 14:55:00 GMT+0530 (2) userchoices= survives | dies (3) minimumsubscriptionamount=0.04 (4) maximumsubscriptionamount=1 (5) contractAmount=0.02 end-contract-conditions + ''', + ''' + create 0 teega# + ''' +] + + +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) + +def parse_flodata(text, blockinfo, net): + if net == 'testnet': + is_testnet = True + else: + is_testnet = False + + if text == '': + return outputreturn('noise') + + clean_text, processed_text = text_preprocessing(text) + # System state + print("Processing stateF") + stateF_mapping = isStateF(processed_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') + + isNFT = check_word_existence_instring('nft', processed_text) + + isInfinite = check_word_existence_instring('infinite-token', processed_text) + tokenamount = apply_rule1(extractAmount_rule_new, processed_text) + + ## Cannot be NFT and normal token and infinite token. Find what are the conflicts + # if its an NFT then tokenamount has to be integer and infinite keyword should not be present + # if its a normal token then isNFT and isInfinite should be None/False and token amount has to be present + # if its an infinite token then tokenamount should be None and isNFT should be None/False + # The supply of tokenAmount cannot be 0 + + ################################################## + + if (not tokenamount and not isInfinite) or (isNFT and not tokenamount.is_integer() and not isInfinite) or (isInfinite and tokenamount is not False and isNFT is not False) or tokenamount<=0: + return outputreturn('noise') + operation = apply_rule1(selectCategory, processed_text, send_category, create_category) + if operation == 'category1' and tokenamount is not None: + if isNFT: + return outputreturn('nft_transfer',f"{processed_text}", f"{tokenname}", tokenamount, stateF_mapping) + else: + return outputreturn('token_transfer',f"{processed_text}", f"{tokenname}", tokenamount, stateF_mapping) + elif operation == 'category2': + if isInfinite: + return outputreturn('infinite_token_create',f"{processed_text}", f"{tokenname}", stateF_mapping) + else: + if tokenamount is None: + return outputreturn('noise') + if isNFT: + nft_hash = extract_NFT_hash(clean_text) + if nft_hash is False: + return outputreturn('noise') + return outputreturn('nft_create',f"{processed_text}", f"{tokenname}", tokenamount, f"{nft_hash}", stateF_mapping) + else: + return outputreturn('token_incorporation',f"{processed_text}", f"{first_classification['wordlist'][0][:-1]}", tokenamount, stateF_mapping) + else: + return outputreturn('noise') + + 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, is_testnet): + return outputreturn('noise') + + contract_conditions = extract_contract_conditions(processed_text, contract_type, contract_token, blocktime=blockinfo['time']) + if contract_conditions == False or not resolve_incategory_conflict(contract_conditions,[['userchoices','payeeAddress']]): + return outputreturn('noise') + else: + contractAmount = '' + if 'contractAmount' in contract_conditions.keys(): + contractAmount = contract_conditions['contractAmount'] + try: + if float(contractAmount)<=0: + return outputreturn('noise') + except: + return outputreturn('noise') + minimum_subscription_amount = '' + if 'minimumsubscriptionamount' in contract_conditions.keys(): + minimum_subscription_amount = contract_conditions['minimumsubscriptionamount'] + try: + if float(minimum_subscription_amount)<=0: + return outputreturn('noise') + except: + return outputreturn('noise') + maximum_subscription_amount = '' + if 'maximumsubscriptionamount' in contract_conditions.keys(): + maximum_subscription_amount = contract_conditions['maximumsubscriptionamount'] + try: + if float(maximum_subscription_amount)<=0: + return outputreturn('noise') + 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"{contractAmount}", f"{minimum_subscription_amount}" , f"{maximum_subscription_amount}", f"{contract_conditions['userchoices']}", f"{contract_conditions['expiryTime']}", stateF_mapping) + elif 'payeeAddress' in contract_conditions.keys(): + contract_conditions['payeeAddress'] = find_word_index_fromstring(clean_text,contract_conditions['payeeAddress']) + # check if colon exists in the payeeAddress string + if ':' in contract_conditions['payeeAddress']: + colon_split = contract_conditions['payeeAddress'].split(':') + if len(colon_split)%2 != 0: + return outputreturn('noise') + split_total = 0 + payeeAddress_split_dictionary = {} + for idx, item in enumerate(colon_split): + if idx%2 == 0: + # check if floid + if not check_flo_address(item, is_testnet): + return outputreturn('noise') + if idx%2 == 1: + # check if number + try: + item = float(item) + if item <= 0: + return outputreturn('noise') + payeeAddress_split_dictionary[colon_split[idx-1]] = item + split_total += item + except: + return outputreturn('noise') + if split_total != 100: + return outputreturn('noise') + else: + contract_conditions['payeeAddress'] = payeeAddress_split_dictionary + return outputreturn('one-time-event-time-smartcontract-incorporation',f"{contract_token}", f"{contract_name}", f"{contract_address}", f"{clean_text}", f"{contractAmount}", f"{minimum_subscription_amount}" , f"{maximum_subscription_amount}", contract_conditions['payeeAddress'], f"{contract_conditions['expiryTime']}", stateF_mapping) + else: + if not check_flo_address(contract_conditions['payeeAddress'], is_testnet): + return outputreturn('noise') + else: + contract_conditions['payeeAddress'] = {f"{contract_conditions['payeeAddress']}":100} + return outputreturn('one-time-event-time-smartcontract-incorporation',f"{contract_token}", f"{contract_name}", f"{contract_address}", f"{clean_text}", f"{contractAmount}", f"{minimum_subscription_amount}" , f"{maximum_subscription_amount}", contract_conditions['payeeAddress'], f"{contract_conditions['expiryTime']}", stateF_mapping) + + 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, is_testnet): + return outputreturn('noise') + + if operation == 'category1': + tokenamount = apply_rule1(extractAmount_rule_new1, processed_text, 'userchoice:', 'pre') + if not tokenamount: + return outputreturn('noise') + try: + if float(tokenamount)<=0: + return outputreturn('noise') + except: + 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}", stateF_mapping) + + elif operation == 'category2': + tokenamount = apply_rule1(extractAmount_rule_new1, processed_text, 'deposit-conditions:', 'pre') + if not tokenamount: + return outputreturn('noise') + try: + if float(tokenamount)<=0: + return outputreturn('noise') + except: + return outputreturn('noise') + deposit_conditions = extract_deposit_conditions(processed_text, blocktime=blockinfo['time']) + if not deposit_conditions: + return outputreturn("noise") + return outputreturn('continuos-event-token-swap-deposit', f"{tokenname}", tokenamount, f"{contract_name}", f"{clean_text}", f"{deposit_conditions['expiryTime']}", stateF_mapping) + + 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') + try: + if float(tokenamount)<=0: + return outputreturn('noise') + except: + 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, is_testnet): + 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}", stateF_mapping) + + 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}", stateF_mapping) + + 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, is_testnet): + 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['selling_token']) + if contract_conditions['priceType']=="'determined'" or contract_conditions['priceType']=='"determined"' or contract_conditions['priceType']=="determined" or contract_conditions['priceType']=="'predetermined'" or contract_conditions['priceType']=='"predetermined"' or contract_conditions['priceType']=="predetermined" or contract_conditions['priceType']=="dynamic": + assert float(contract_conditions['price'])>0 + else: + #assert check_flo_address(find_original_case(contract_conditions['priceType'], clean_text), is_testnet) + assert contract_conditions['priceType'] == 'statef' + 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']}", stateF_mapping) + + return outputreturn('noise') \ No newline at end of file From ab84bca56042413cef3dcddd59c00ca318421ad0 Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Mon, 3 Apr 2023 17:45:12 +0000 Subject: [PATCH 11/46] Fixed block confirmations on the version 2 of the API --- ranchimallflo_api.py | 212 +++++++++++++++++++------------------------ 1 file changed, 92 insertions(+), 120 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 77dd2ca..adc9092 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -41,8 +41,7 @@ def check_integer(value): def retryRequest(tempserverlist, apicall): if len(tempserverlist) != 0: try: - response = requests.get( - '{}api/{}'.format(tempserverlist[0], apicall)) + response = requests.get('{}api/{}'.format(tempserverlist[0], apicall)) except: tempserverlist.pop(0) return retryRequest(tempserverlist, apicall) @@ -56,6 +55,7 @@ def retryRequest(tempserverlist, apicall): print("None of the APIs are responding for the call {}".format(apicall)) sys.exit(0) + def multiRequest(apicall, net): testserverlist = ['http://0.0.0.0:9000/', 'https://testnet.flocha.in/', 'https://testnet-flosight.duckdns.org/'] mainserverlist = ['http://0.0.0.0:9001/', 'https://livenet.flocha.in/', 'https://testnet-flosight.duckdns.org/'] @@ -64,6 +64,7 @@ def multiRequest(apicall, net): elif net == 'testnet': return retryRequest(testserverlist, apicall) + def blockdetailhelper(blockdetail): if blockdetail.isdigit(): blockHash = None @@ -71,16 +72,14 @@ def blockdetailhelper(blockdetail): else: blockHash = str(blockdetail) blockHeight = None - + # open the latest block database conn = sqlite3.connect(os.path.join(dbfolder, 'latestCache.db')) c = conn.cursor() - if blockHash: c.execute(f"select jsonData from latestBlocks where blockHash='{blockHash}'") elif blockHeight: c.execute(f"select jsonData from latestBlocks where blockNumber='{blockHeight}'") - blockJson = c.fetchall() return blockJson @@ -88,12 +87,18 @@ def transactiondetailhelper(transactionHash): # open the latest block database conn = sqlite3.connect(os.path.join(dbfolder, 'latestCache.db')) c = conn.cursor() - c.execute(f"select jsonData, parsedFloData, transactionType, db_reference from latestTransactions where transactionHash='{transactionHash}'") transactionJsonData = c.fetchall() - return transactionJsonData +def update_transaction_confirmations(transactionJson): + url = f"{apiUrl}tx/{transactionJson['txid']}" + response = requests.get(url) + if response.status_code == 200: + response_data = response.json() + transactionJson['confirmations'] = response_data['confirmations'] + return transactionJson + def smartContractInfo_output(contractName, contractAddress, contractType, subtype): if contractType == 'continuos-event' and contractType == 'tokenswap': pass @@ -101,7 +106,7 @@ def smartContractInfo_output(contractName, contractAddress, contractType, subtyp pass elif contractType == 'one-time-event' and contractType == 'timetrigger': pass - + def return_smart_contracts(connection, contractName=None, contractAddress=None): # find all the contracts details if contractName and contractAddress: @@ -243,7 +248,6 @@ async def systemData(): validatedBlockCount = c.execute('select count(distinct blockNumber) from latestBlocks').fetchall()[0][0] validatedTransactionCount = c.execute('select count(distinct transactionHash) from latestTransactions').fetchall()[0][0] conn.close() - return jsonify(systemAddressCount=tokenAddressCount, systemBlockCount=validatedBlockCount, systemTransactionCount=validatedTransactionCount, systemSmartContractCount=contractCount, systemTokenCount=tokenCount, lastscannedblock=lastscannedblock, result='ok') @@ -260,17 +264,15 @@ async def getTokenList(): 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', description='token name hasnt been passed') - + dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) @@ -298,9 +300,7 @@ async def getTokenInfo(): tempdict['transactionHash'] = item[4] associatedContractList.append(tempdict) - return jsonify(result='ok', token=token, incorporationAddress=incorporationRow[1], tokenSupply=incorporationRow[3], - time=incorporationRow[6], blockchainReference=incorporationRow[7], - activeAddress_no=numberOf_distinctAddresses, totalTransactions=numberOf_transactions, associatedSmartContracts=associatedContractList) + return jsonify(result='ok', token=token, incorporationAddress=incorporationRow[1], tokenSupply=incorporationRow[3], time=incorporationRow[6], blockchainReference=incorporationRow[7], activeAddress_no=numberOf_distinctAddresses, totalTransactions=numberOf_transactions, associatedSmartContracts=associatedContractList) @app.route('/api/v1.0/getTokenTransactions', methods=['GET']) @@ -312,7 +312,7 @@ async def getTokenTransactions(): if token is None: return jsonify(result='error', description='token name hasnt been passed') - + dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) @@ -320,7 +320,7 @@ async def getTokenTransactions(): c = conn.cursor() else: return jsonify(result='error', description='token doesn\'t exist') - + if senderFloAddress and not destFloAddress: if limit is None: c.execute('SELECT jsonData, parsedFloData FROM transactionHistory WHERE sourceFloAddress="{}" ORDER BY id DESC'.format(senderFloAddress)) @@ -339,19 +339,18 @@ async def getTokenTransactions(): else: if limit is None: - c.execute( - 'SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC') + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC') else: - c.execute( - 'SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT {}'.format(limit)) + c.execute('SELECT jsonData, parsedFloData FROM transactionHistory ORDER BY id DESC LIMIT {}'.format(limit)) transactionJsonData = c.fetchall() conn.close() rowarray_list = {} for row in transactionJsonData: - temp = {} - temp['transactionDetails'] = json.loads(row[0]) - temp['parsedFloData'] = json.loads(row[1]) - rowarray_list[temp['transactionDetails']['txid']] = temp + transactions_object = {} + transactions_object['transactionDetails'] = json.loads(row[0]) + transactions_object['transactionDetails'] = update_transaction_confirmations(transactions_object['transactionDetails']) + transactions_object['parsedFloData'] = json.loads(row[1]) + rowarray_list[transactions_object['transactionDetails']['txid']] = transactions_object return jsonify(result='ok', token=token, transactions=rowarray_list) @@ -360,17 +359,16 @@ async def getTokenBalances(): token = request.args.get('token') if token is None: return jsonify(result='error', description='token name hasnt been passed') - + dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) c = conn.cursor() else: return jsonify(result='error', description='token doesn\'t exist') - c.execute( - 'SELECT address,SUM(transferBalance) FROM activeTable GROUP BY address') + c.execute('SELECT address,SUM(transferBalance) FROM activeTable GROUP BY address') addressBalances = c.fetchall() - + returnList = {} for address in addressBalances: @@ -385,22 +383,18 @@ async def getFloAddressInfo(): floAddress = request.args.get('floAddress') if floAddress is None: return jsonify(description='floAddress hasn\'t been passed'), 400 - + 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)) + c.execute('select token from tokenAddressMapping where tokenAddress="{}"'.format(floAddress)) tokenNames = c.fetchall() - - c.execute( - f"select contractName, status, tokenIdentification, contractType, transactionHash, blockNumber, blockHash from activecontracts where contractAddress='{floAddress}'") + c.execute(f"select contractName, status, tokenIdentification, contractType, transactionHash, blockNumber, blockHash from activecontracts where contractAddress='{floAddress}'") incorporatedContracts = c.fetchall() - + if len(tokenNames) != 0: detailList = {} - for token in tokenNames: token = token[0] dblocation = dbfolder + '/tokens/' + str(token) + '.db' @@ -408,13 +402,11 @@ async def getFloAddressInfo(): tempdict = {} conn = sqlite3.connect(dblocation) c = conn.cursor() - c.execute( - 'SELECT SUM(transferBalance) FROM activeTable WHERE address="{}"'.format(floAddress)) + c.execute('SELECT SUM(transferBalance) FROM activeTable WHERE address="{}"'.format(floAddress)) balance = c.fetchall()[0][0] tempdict['balance'] = balance tempdict['token'] = token detailList[token] = tempdict - else: # Address is not associated with any token return jsonify(result='error', description='FLO address is not associated with any tokens') @@ -432,11 +424,10 @@ async def getFloAddressInfo(): tempdict['blockNumber'] = contract[5] tempdict['blockHash'] = contract[6] incorporatedSmartContracts.append(tempdict) - + return jsonify(result='ok', floAddress=floAddress, floAddressBalances=detailList, incorporatedSmartContracts=incorporatedContracts) else: - return jsonify(result='ok', floAddress=floAddress, floAddressBalances=detailList, - incorporatedSmartContracts=None) + return jsonify(result='ok', floAddress=floAddress, floAddressBalances=detailList, incorporatedSmartContracts=None) @app.route('/api/v1.0/getFloAddressBalance', methods=['GET']) @@ -500,13 +491,13 @@ async def getFloAddressTransactions(): if floAddress is None: return jsonify(result='error', description='floAddress has not been passed') - + if token is None: 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)) + c.execute('SELECT token FROM tokenAddressMapping WHERE tokenAddress="{}"'.format(floAddress)) tokenNames = c.fetchall() else: dblocation = dbfolder + '/tokens/' + str(token) + '.db' @@ -514,10 +505,9 @@ async def getFloAddressTransactions(): tokenNames = [[str(token), ]] else: return jsonify(result='error', description='token doesn\'t exist') - + if len(tokenNames) != 0: allTransactionList = {} - for tokenname in tokenNames: tokenname = tokenname[0] dblocation = dbfolder + '/tokens/' + str(tokenname) + '.db' @@ -533,11 +523,11 @@ async def getFloAddressTransactions(): conn.close() for row in transactionJsonData: - temp = {} - temp['transactionDetails'] = json.loads(row[0]) - temp['parsedFloData'] = json.loads(row[1]) - allTransactionList[temp['transactionDetails'] - ['txid']] = temp + transactions_object = {} + transactions_object['transactionDetails'] = json.loads(row[0]) + transactions_object['transactionDetails'] = update_transaction_confirmations(transactions_object['transactionDetails']) + transactions_object['parsedFloData'] = json.loads(row[1]) + allTransactionList[transactions_object['transactionDetails']['txid']] = transactions_object if token is None: return jsonify(result='ok', floAddress=floAddress, transactions=allTransactionList) @@ -1047,10 +1037,11 @@ async def getsmartcontracttransactions(): returnval = {} for item in result: - temp = {} - temp['transactionDetails'] = json.loads(item[0]) - temp['parsedFloData'] = json.loads(item[1]) - returnval[temp['transactionDetails']['txid']] = temp + transactions_object = {} + transactions_object['transactionDetails'] = json.loads(item[0]) + transactions_object['transactionDetails'] = update_transaction_confirmations(transactions_object['transactionDetails']) + transactions_object['parsedFloData'] = json.loads(item[1]) + returnval[transactions_object['transactionDetails']['txid']] = transactions_object return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, contractTransactions=returnval) @@ -1070,11 +1061,10 @@ async def getblockdetails(blockdetail): @app.route('/api/v1.0/getTransactionDetails/', methods=['GET']) async def gettransactiondetails(transactionHash): - transactionJsonData = transactiondetailhelper(transactionHash) - if len(transactionJsonData) != 0: transactionJson = json.loads(transactionJsonData[0][0]) + transactionJson = update_transaction_confirmations(transactionJson) parseResult = json.loads(transactionJsonData[0][1]) return jsonify(parsedFloData=parseResult, transactionDetails=transactionJson, transactionHash=transactionHash, result='ok') @@ -1102,6 +1092,7 @@ async def getLatestTransactionDetails(): item = list(item) tx_parsed_details = {} tx_parsed_details['transactionDetails'] = json.loads(item[3]) + tx_parsed_details['transactionDetails'] = update_transaction_confirmations(tx_parsed_details['transactionDetails']) tx_parsed_details['parsedFloData'] = json.loads(item[5]) tx_parsed_details['parsedFloData']['transactionType'] = item[4] tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) @@ -1115,6 +1106,7 @@ async def getLatestTransactionDetails(): item = list(item) tx_parsed_details = {} tx_parsed_details['transactionDetails'] = json.loads(item[3]) + tx_parsed_details['transactionDetails'] = update_transaction_confirmations(tx_parsed_details['transactionDetails']) tx_parsed_details['parsedFloData'] = json.loads(item[5]) tx_parsed_details['parsedFloData']['transactionType'] = item[4] tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) @@ -1125,14 +1117,13 @@ async def getLatestTransactionDetails(): @app.route('/api/v1.0/getLatestBlockDetails', methods=['GET']) async def getLatestBlockDetails(): limit = request.args.get('limit') - 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' - + if limit is None: c.execute('''SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT 4) ORDER BY id ASC;''') else: @@ -1156,6 +1147,7 @@ async def getblocktransactions(blockdetail): for i in range(len(blocktxlist)): temptx = transactiondetailhelper(blocktxlist[i]) transactionJson = json.loads(temptx[0][0]) + transactionJson = update_transaction_confirmations(transactionJson) parseResult = json.loads(temptx[0][1]) blocktxs[blocktxlist[i]] = { "parsedFloData" : parseResult, @@ -1181,7 +1173,7 @@ async def categoriseString(urlstring): tokenfolder = os.path.join(dbfolder, 'tokens') onlyfiles = [f[:-3] for f in os.listdir(tokenfolder) if os.path.isfile(os.path.join(tokenfolder, f))] - + if urlstring.lower() in onlyfiles: return jsonify(type='token') else: @@ -1189,8 +1181,7 @@ async def categoriseString(urlstring): conn = sqlite3.connect(contractfolder) conn.row_factory = lambda cursor, row: row[0] c = conn.cursor() - contractList = c.execute( - 'select contractname from activeContracts').fetchall() + contractList = c.execute('select contractname from activeContracts').fetchall() if urlstring.lower() in contractList: return jsonify(type='smartContract') @@ -1209,10 +1200,8 @@ async def getTokenSmartContractList(): # list of smart contracts conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) c = conn.cursor() - contractList = [] - - c.execute('select * from activecontracts') + c.execute('SELECT * FROM activecontracts') allcontractsDetailList = c.fetchall() for idx, contract in enumerate(allcontractsDetailList): contractDict = {} @@ -1229,7 +1218,6 @@ async def getTokenSmartContractList(): contractDict['expiryDate'] = contract[10] if contract[11]: contractDict['closeDate'] = contract[11] - contractList.append(contractDict) return jsonify(tokens=filelist, smartContracts=contractList, result='ok') @@ -1249,14 +1237,14 @@ async def info(): contractCount = c.execute('SELECT COUNT(distinct contractName) FROM contractAddressMapping').fetchall()[0][0] lastscannedblock = int(c.execute("SELECT value FROM systemData WHERE attribute=='lastblockscanned'").fetchall()[0][0]) conn.close() - + # query for total number of validated blocks conn = sqlite3.connect(os.path.join(dbfolder, 'latestCache.db')) c = conn.cursor() validatedBlockCount = c.execute('SELECT COUNT(distinct blockNumber) FROM latestBlocks').fetchall()[0][0] validatedTransactionCount = c.execute('SELECT COUNT(distinct transactionHash) FROM latestTransactions').fetchall()[0][0] conn.close() - + return jsonify(systemAddressCount=tokenAddressCount, systemBlockCount=validatedBlockCount, systemTransactionCount=validatedTransactionCount, systemSmartContractCount=contractCount, systemTokenCount=tokenCount, lastscannedblock=lastscannedblock), 200 @@ -1267,6 +1255,7 @@ async def broadcastTx_v2(raw_transaction_hash): # FLO TOKEN APIs + @app.route('/api/v2/tokenList', methods=['GET']) async def tokenList(): filelist = [] @@ -1280,7 +1269,7 @@ async def tokenList(): async def tokenInfo(token): if token is None: return jsonify(description='Token name hasnt been passed'), 400 - + # todo : input validation dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): @@ -1297,7 +1286,7 @@ async def tokenInfo(token): c.execute('SELECT contractName, contractAddress, blockNumber, blockHash, transactionHash FROM tokenContractAssociation') associatedContracts = c.fetchall() conn.close() - + associatedContractList = [] for item in associatedContracts: tempdict = {} @@ -1362,10 +1351,11 @@ async def tokenTransactions(token): rowarray_list = [] sorted_list = sorted(transactionJsonData, key=itemgetter('blocktime'), reverse=True) for row in sorted_list: - temp = {} - temp['transactionDetails'] = json.loads(row[0]) - temp['parsedFloData'] = json.loads(row[1]) - rowarray_list.append(temp) + transactions_object = {} + transactions_object['transactionDetails'] = json.loads(row[0]) + transactions_object['transactionDetails'] = update_transaction_confirmations(transactions_object['transactionDetails']) + transactions_object['parsedFloData'] = json.loads(row[1]) + rowarray_list.append(transactions_object) return jsonify(token=token, transactions=rowarray_list), 200 @@ -1382,7 +1372,6 @@ async def tokenBalances(token): return jsonify(description="Token doesn't exist"), 404 c.execute('SELECT address,SUM(transferBalance) FROM activeTable GROUP BY address') addressBalances = c.fetchall() - returnList = {} for address in addressBalances: returnList[address[0]] = address[1] @@ -1398,7 +1387,7 @@ async def floAddressInfo(floAddress): # input validation if not check_flo_address(floAddress, is_testnet): return jsonify(description='floAddress validation failed'), 400 - + dblocation = dbfolder + '/system.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) @@ -1451,7 +1440,7 @@ async def floAddressBalance(floAddress): # input validation if not check_flo_address(floAddress, is_testnet): return jsonify(description='floAddress validation failed'), 400 - + token = request.args.get('token') if token is None: dblocation = dbfolder + '/system.db' @@ -1501,7 +1490,7 @@ async def floAddressTransactions(floAddress): limit = request.args.get('limit') if limit is not None and not check_integer(limit): return jsonify(description='limit validation failed'), 400 - + token = request.args.get('token') if token is None: dblocation = dbfolder + '/system.db' @@ -1516,7 +1505,7 @@ async def floAddressTransactions(floAddress): tokenNames = [[str(token), ]] else: return jsonify(description="Token doesn't exist"), 404 - + if len(tokenNames) != 0: allTransactionList = [] for tokenname in tokenNames: @@ -1538,6 +1527,7 @@ async def floAddressTransactions(floAddress): for row in allTransactionList: tx = {} tx['transactionDetails'] = json.loads(row[0]) + tx['transactionDetails'] = update_transaction_confirmations(tx['transactionDetails']) tx['parsedFloData'] = json.loads(row[1]) rowarray_list.append(tx) if token is None: @@ -1593,7 +1583,7 @@ async def getContractInfo_v2(): contractAddress = contractAddress.strip() if not check_flo_address(contractAddress, is_testnet): return jsonify(description='contractAddress validation failed'), 400 - + contractStructure = fetchContractStructure(contractName, contractAddress) if contractStructure: returnval = contractStructure @@ -1607,14 +1597,12 @@ async def getContractInfo_v2(): returnval['totalHonorAmount'] = participation_details[0][2] c.execute('SELECT COUNT(DISTINCT transactionHash) FROM contractdeposits') returnval['numberOfDeposits'] = c.fetchall()[0][0] - elif contractStructure['contractType'] == 'one-time-event' and 'exitconditions' in contractStructure.keys(): returnval['userChoice'] = contractStructure['exitconditions'] returnval.pop('exitconditions') conn, c = create_database_connection('system_dbs') - c.execute('SELECT status, incorporationDate, expiryDate, closeDate FROM activecontracts WHERE contractName=="{}" AND contractAddress=="{}"'.format(contractName.strip(), contractAddress.strip())) + c.execute('SELECT status, incorporationDate, expiryDate, closeDate FROM activecontracts WHERE contractName=="{}" AND contractAddress=="{}"'.format(contractName, contractAddress)) results = c.fetchall() - if len(results) == 1: for result in results: returnval['status'] = result[0] @@ -1623,7 +1611,6 @@ async def getContractInfo_v2(): returnval['expiryDate'] = result[2] if result[3]: returnval['closeDate'] = result[3] - elif contractStructure['contractType'] == 'one-time-event' and 'payeeAddress' in contractStructure.keys(): pass @@ -1638,11 +1625,11 @@ async def getcontractparticipants_v2(): if contractName is None: return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 contractName = contractName.strip().lower() - + contractAddress = request.args.get('contractAddress') - contractAddress = contractAddress.strip() if contractAddress is None: return jsonify(description='Smart Contract\'s address hasn\'t been passed'), 400 + contractAddress = contractAddress.strip() if not check_flo_address(contractAddress, is_testnet): return jsonify(description='contractAddress validation failed'), 400 @@ -1662,7 +1649,6 @@ async def getcontractparticipants_v2(): returnval = {} for row in result: returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4], 'winningAmount': row[5], 'tokenIdentification': token} - else: c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash FROM contractparticipants') result = c.fetchall() @@ -1670,7 +1656,6 @@ async def getcontractparticipants_v2(): returnval = {} for row in result: returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4]} - elif 'payeeAddress' in contractStructure: # contract is of the type internal trigger c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash FROM contractparticipants') @@ -1679,7 +1664,6 @@ async def getcontractparticipants_v2(): returnval = {} for row in result: returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'transactionHash': row[4]} - elif contractStructure['contractType'] == 'continuos-event' and contractStructure['subtype'] == 'tokenswap': c.execute('SELECT * FROM contractparticipants') contract_participants = c.fetchall() @@ -1695,7 +1679,6 @@ async def getcontractparticipants_v2(): 'swapAmount': row[7] } conn.close() - return jsonify(contractName=contractName, contractAddress=contractAddress, participantInfo=returnval), 200 else: return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404 @@ -1709,28 +1692,27 @@ async def participantDetails(): return jsonify(description='floAddress validation failed'), 400 contractName = request.args.get('contractName') - contractName = contractName.strip().lower() - contractAddress = request.args.get('contractAddress') - contractAddress = contractAddress.strip() + if contractAddress is not None and not check_flo_address(contractAddress, is_testnet): return jsonify(description='contractAddress validation failed'), 400 + contractAddress = contractAddress.strip() if (contractName and contractAddress is None) or (contractName is None and contractAddress): return jsonify(description='pass both, contractName and contractAddress as url parameters'), 400 + contractName = contractName.strip().lower() dblocation = os.path.join(dbfolder, 'system.db') if os.path.isfile(dblocation): # Make db connection and fetch data conn = sqlite3.connect(dblocation) c = conn.cursor() - if contractName is not None: c.execute(f'SELECT * FROM contractAddressMapping WHERE address="{floAddress}" AND addressType="participant" AND contractName="{contractName}" AND contractAddress="{contractAddress}"') else: c.execute(f'SELECT * FROM contractAddressMapping WHERE address="{floAddress}" AND addressType="participant"') participant_address_contracts = c.fetchall() - + if len(participant_address_contracts) != 0: participationDetailsList = [] for contract in participant_address_contracts: @@ -1739,7 +1721,6 @@ async def participantDetails(): # Make db connection and fetch contract structure conn = sqlite3.connect(contract_db) c = conn.cursor() - # Get details of the type of Smart Contract c.execute('SELECT attribute,value FROM contractstructure') result = c.fetchall() @@ -1762,7 +1743,6 @@ async def participantDetails(): # what is a api detail c.execute('SELECT * FROM contractparticipants WHERE participantAddress=?',(floAddress,)) participant_details = c.fetchall() - if len(participant_details) > 0: participationList = [] for participation in participant_details: @@ -1778,9 +1758,8 @@ async def participantDetails(): detailsDict['blockNumber'] = participation[5] detailsDict['blockHash'] = participation[6] participationList.append(detailsDict) - participationDetailsList.append(participationList) - + elif contractStructure['contractType']=='one-time-event' and 'payeeAddress' in contractStructure.keys(): # normal results conn = sqlite3.connect(dblocation) @@ -1805,7 +1784,7 @@ async def participantDetails(): detailsDict['closeDate'] = temp[0][7] # check if the contract has been closed - contractDbName = '{}-{}.db'.format(detailsDict['contractName'].strip(), detailsDict['contractAddress'].strip()) + contractDbName = '{}-{}.db'.format(detailsDict['contractName'], detailsDict['contractAddress']) filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) if os.path.isfile(filelocation): # Make db connection and fetch data @@ -1858,7 +1837,7 @@ async def participantDetails(): detailsDict['closeDate'] = temp[0][7] # check if the contract has been closed - contractDbName = '{}-{}.db'.format(detailsDict['contractName'].strip(), detailsDict['contractAddress'].strip()) + contractDbName = '{}-{}.db'.format(detailsDict['contractName'], detailsDict['contractAddress']) filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) if os.path.isfile(filelocation): # Make db connection and fetch data @@ -1908,22 +1887,22 @@ async def participantDetails(): @app.route('/api/v2/smartContractTransactions', methods=['GET']) async def smartcontracttransactions(): contractName = request.args.get('contractName') - contractName = contractName.strip().lower() if contractName is None: return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 + contractName = contractName.strip().lower() contractAddress = request.args.get('contractAddress') - contractAddress = contractAddress.strip() if contractAddress is None: return jsonify(description='Smart Contract\'s address hasn\'t been passed'), 400 + contractAddress = contractAddress.strip() if not check_flo_address(contractAddress, is_testnet): return jsonify(description='contractAddress validation failed'), 400 - + limit = request.args.get('limit') if limit is not None and not check_integer(limit): return jsonify(description='limit validation failed'), 400 - - contractDbName = '{}-{}.db'.format(contractName.strip(), contractAddress.strip()) + + contractDbName = '{}-{}.db'.format(contractName, contractAddress) filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) if os.path.isfile(filelocation): @@ -1931,18 +1910,18 @@ async def smartcontracttransactions(): conn = sqlite3.connect(filelocation) c = conn.cursor() if limit is None: - c.execute('SELECT jsonData, parsedFloData FROM contractTransactionHistory ORDER BY blocktime DESC') + c.execute('SELECT jsonData, parsedFloData FROM contractTransactionHistory ORDER BY time DESC') else: - c.execute(f'SELECT jsonData, parsedFloData FROM contractTransactionHistory ORDER BY blocktime DESC LIMIT {limit}') + c.execute(f'SELECT jsonData, parsedFloData FROM contractTransactionHistory ORDER BY time DESC LIMIT {limit}') result = c.fetchall() conn.close() returnval = [] for item in result: tx = {} tx['transactionDetails'] = json.loads(item[0]) + tx['transactionDetails'] = update_transaction_confirmations(tx['transactionDetails']) tx['parsedFloData'] = json.loads(item[1]) returnval.append(tx) - return jsonify(contractName=contractName, contractAddress=contractAddress, contractTransactions=returnval), 200 else: return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404 @@ -1951,7 +1930,6 @@ async def smartcontracttransactions(): @app.route('/api/v2/blockDetails/', methods=['GET']) async def blockdetails(blockHash): # todo - validate blockHash - blockJson = blockdetailhelper(blockHash) if len(blockJson) != 0: blockJson = json.loads(blockJson[0][0]) @@ -1963,10 +1941,10 @@ async def blockdetails(blockHash): @app.route('/api/v2/transactionDetails/', methods=['GET']) async def transactiondetails1(transactionHash): # todo - validate transactionHash - transactionJsonData = transactiondetailhelper(transactionHash) if len(transactionJsonData) != 0: transactionJson = json.loads(transactionJsonData[0][0]) + transactionJson = update_transaction_confirmations(transactionJson) parseResult = json.loads(transactionJsonData[0][1]) operation = transactionJsonData[0][2] db_reference = transactionJsonData[0][3] @@ -1989,7 +1967,7 @@ async def transactiondetails1(transactionHash): operationDetails['depositHonors']['count'] = len(deposit_honors) for deposit_honor in deposit_honors: operationDetails['depositHonors']['list'].append({'honor_amount':deposit_honor[0],'blockNumber':deposit_honor[1]}) - + c.execute("SELECT depositBalance FROM contractdeposits WHERE id=(SELECT max(id) FROM contractdeposits WHERE transactionHash=?)",(transactionJson['txid'],)) depositBalance = c.fetchall() operationDetails['depositBalance'] = depositBalance[0][0] @@ -2064,6 +2042,7 @@ async def latestTransactionDetails(): item = list(item) tx_parsed_details = {} tx_parsed_details['transactionDetails'] = json.loads(item[3]) + tx_parsed_details['transactionDetails'] = update_transaction_confirmations(tx_parsed_details['transactionDetails']) tx_parsed_details['parsedFloData'] = json.loads(item[5]) tx_parsed_details['parsedFloData']['transactionType'] = item[4] tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) @@ -2131,7 +2110,6 @@ async def categoriseString_v2(urlstring): tokenfolder = os.path.join(dbfolder, 'tokens') onlyfiles = [f[:-3] for f in os.listdir(tokenfolder) if os.path.isfile(os.path.join(tokenfolder, f))] - if urlstring.lower() in onlyfiles: return jsonify(type='token'), 200 else: @@ -2158,9 +2136,7 @@ async def tokenSmartContractList(): # list of smart contracts conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) c = conn.cursor() - contractList = [] - c.execute('select * from activecontracts') allcontractsDetailList = c.fetchall() for idx, contract in enumerate(allcontractsDetailList): @@ -2178,9 +2154,7 @@ async def tokenSmartContractList(): contractDict['expiryDate'] = contract[10] if contract[11]: contractDict['closeDate'] = contract[11] - contractList.append(contractDict) - return jsonify(tokens=filelist, smartContracts=contractList), 200 @@ -2242,11 +2216,9 @@ async def priceData(): ratepairs = c.execute('select ratepair, price from ratepairs') ratepairs = ratepairs.fetchall() prices = {} - for ratepair in ratepairs: ratepair = list(ratepair) prices[ratepair[0]] = ratepair[1] - return jsonify(prices=prices), 200 From 1fde179a32e0a92e159e7018d43978442e4e23ca Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Sat, 8 Apr 2023 09:29:02 +0000 Subject: [PATCH 12/46] Fixed the change in transactions columns, from blocktime -> time --- ranchimallflo_api.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index adc9092..aa279a4 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -1327,29 +1327,29 @@ async def tokenTransactions(token): if senderFloAddress and not destFloAddress: if limit is None: - c.execute('SELECT jsonData, parsedFloData, blocktime FROM transactionHistory WHERE sourceFloAddress="{}"'.format(senderFloAddress)) + c.execute('SELECT jsonData, parsedFloData, time FROM transactionHistory WHERE sourceFloAddress="{}"'.format(senderFloAddress)) else: - c.execute('SELECT jsonData, parsedFloData, blocktime FROM transactionHistory WHERE sourceFloAddress="{}" LIMIT {}'.format(senderFloAddress, limit)) + c.execute('SELECT jsonData, parsedFloData, time FROM transactionHistory WHERE sourceFloAddress="{}" LIMIT {}'.format(senderFloAddress, limit)) elif not senderFloAddress and destFloAddress: if limit is None: - c.execute('SELECT jsonData, parsedFloData, blocktime FROM transactionHistory WHERE destFloAddress="{}"'.format(destFloAddress)) + c.execute('SELECT jsonData, parsedFloData, time FROM transactionHistory WHERE destFloAddress="{}"'.format(destFloAddress)) else: - c.execute('SELECT jsonData, parsedFloData, blocktime FROM transactionHistory WHERE destFloAddress="{}" LIMIT {}'.format(destFloAddress, limit)) + c.execute('SELECT jsonData, parsedFloData, time FROM transactionHistory WHERE destFloAddress="{}" LIMIT {}'.format(destFloAddress, limit)) elif senderFloAddress and destFloAddress: if limit is None: - c.execute('SELECT jsonData, parsedFloData, blocktime FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}"'.format(senderFloAddress, destFloAddress)) + c.execute('SELECT jsonData, parsedFloData, time FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}"'.format(senderFloAddress, destFloAddress)) else: - c.execute('SELECT jsonData, parsedFloData, blocktime FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" LIMIT {}'.format(senderFloAddress, destFloAddress, limit)) + c.execute('SELECT jsonData, parsedFloData, time FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" LIMIT {}'.format(senderFloAddress, destFloAddress, limit)) else: if limit is None: - c.execute('SELECT jsonData, parsedFloData, blocktime FROM transactionHistory') + c.execute('SELECT jsonData, parsedFloData, time FROM transactionHistory') else: - c.execute('SELECT jsonData, parsedFloData, blocktime FROM transactionHistory LIMIT {}'.format(limit)) + c.execute('SELECT jsonData, parsedFloData, time FROM transactionHistory LIMIT {}'.format(limit)) transactionJsonData = c.fetchall() conn.close() rowarray_list = [] - sorted_list = sorted(transactionJsonData, key=itemgetter('blocktime'), reverse=True) + sorted_list = sorted(transactionJsonData, key=itemgetter('time'), reverse=True) for row in sorted_list: transactions_object = {} transactions_object['transactionDetails'] = json.loads(row[0]) @@ -1516,14 +1516,14 @@ async def floAddressTransactions(floAddress): conn.row_factory = sqlite3.Row c = conn.cursor() if limit is None: - c.execute(f'SELECT jsonData, parsedFloData, blocktime FROM transactionHistory WHERE sourceFloAddress="{floAddress}" OR destFloAddress="{floAddress}" ORDER BY blocktime DESC') + c.execute(f'SELECT jsonData, parsedFloData, time FROM transactionHistory WHERE sourceFloAddress="{floAddress}" OR destFloAddress="{floAddress}" ORDER BY time DESC') else: - c.execute(f'SELECT jsonData, parsedFloData, blocktime FROM transactionHistory WHERE sourceFloAddress="{floAddress}" OR destFloAddress="{floAddress}" ORDER BY blocktime DESC LIMIT {limit}') + c.execute(f'SELECT jsonData, parsedFloData, time FROM transactionHistory WHERE sourceFloAddress="{floAddress}" OR destFloAddress="{floAddress}" ORDER BY time DESC LIMIT {limit}') transactionJsonData = c.fetchall() conn.close() allTransactionList = allTransactionList + transactionJsonData rowarray_list = [] - allTransactionList = sorted(allTransactionList, key=itemgetter('blocktime'), reverse=True) + allTransactionList = sorted(allTransactionList, key=itemgetter('time'), reverse=True) for row in allTransactionList: tx = {} tx['transactionDetails'] = json.loads(row[0]) From c46c45e6b98a490398eef646234b1b1c513d7d97 Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Sat, 8 Apr 2023 09:29:23 +0000 Subject: [PATCH 13/46] Updated gitignore and requirements.txt --- .gitignore | 1 + requirements.txt | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 184331f..ffa4bf3 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ config.py .idea/ py3.7/ py3/ +py3.8/ *.db *.code-workspace *.log diff --git a/requirements.txt b/requirements.txt index cd8dbd7..5166ca8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -17,7 +17,7 @@ Jinja2==2.10.1 MarkupSafe==1.1.1 multidict==4.5.2 priority==1.3.0 -pybtc==2.0.9 +pyflo-lib==2.0.9 pycparser==2.19 python-dateutil==2.8.0 Quart==0.10.0 From 26444acca983151977ab0b13ceb5275fbab14f07 Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Sat, 8 Apr 2023 09:56:17 +0000 Subject: [PATCH 14/46] Changed api/v2/latestTransactionDetails parameter to v2 --- ranchimallflo_api.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index aa279a4..d73fc85 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -2020,9 +2020,9 @@ async def transactiondetails1(transactionHash): @app.route('/api/v2/latestTransactionDetails', methods=['GET']) async def latestTransactionDetails(): - numberOfLatestBlocks = request.args.get('numberOfLatestBlocks') - if numberOfLatestBlocks is not None and not check_integer(numberOfLatestBlocks): - return jsonify(description='numberOfLatestBlocks validation failed'), 400 + limit = request.args.get('limit') + if limit is not None and not check_integer(limit): + return jsonify(description='limit validation failed'), 400 dblocation = dbfolder + '/latestCache.db' if os.path.exists(dblocation): @@ -2031,8 +2031,8 @@ async def latestTransactionDetails(): else: return jsonify(description='Latest transactions db doesn\'t exist. This is unusual, please report on https://github.com/ranchimall/ranchimallflo-api'), 500 - if numberOfLatestBlocks is not None: - c.execute('SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT {}) ORDER BY id ASC;'.format(int(numberOfLatestBlocks))) + if limit is not None: + c.execute('SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT {}) ORDER BY id ASC;'.format(int(limit))) else: c.execute('''SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC) ORDER BY id ASC;''') latestTransactions = c.fetchall() From 9731999833b7de7e8728b50a503188ed4c1f1588 Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Sat, 8 Apr 2023 17:31:20 +0000 Subject: [PATCH 15/46] Fixed order to descending for latestBlocks and latestTransactions --- ranchimallflo_api.py | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index d73fc85..db30b22 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -2032,9 +2032,9 @@ async def latestTransactionDetails(): return jsonify(description='Latest transactions db doesn\'t exist. This is unusual, please report on https://github.com/ranchimall/ranchimallflo-api'), 500 if limit is not None: - c.execute('SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT {}) ORDER BY id ASC;'.format(int(limit))) + c.execute('SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC LIMIT {}) ORDER BY id DESC;'.format(int(limit))) else: - c.execute('''SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC) ORDER BY id ASC;''') + c.execute('''SELECT * FROM latestTransactions WHERE blockNumber IN (SELECT DISTINCT blockNumber FROM latestTransactions ORDER BY blockNumber DESC) ORDER BY id DESC;''') latestTransactions = c.fetchall() c.close() tx_list = [] @@ -2064,15 +2064,17 @@ async def latestBlockDetails(): return jsonify(description='Latest transactions db doesn\'t exist. This is unusual, please report on https://github.com/ranchimall/ranchimallflo-api'), 404 if limit is None: - c.execute('''SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT 4) ORDER BY id ASC;''') + c.execute('''SELECT jsonData FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT 4) ORDER BY id DESC;''') else: - c.execute(f'SELECT * FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT {limit}) ORDER BY id ASC;') + c.execute(f'SELECT jsonData FROM ( SELECT * FROM latestBlocks ORDER BY blockNumber DESC LIMIT {limit}) ORDER BY id DESC;') latestBlocks = c.fetchall() c.close() - tempdict = {} + + templst = [] for idx, item in enumerate(latestBlocks): - tempdict[json.loads(item[3])['hash']] = json.loads(item[3]) - return jsonify(result='ok', latestBlocks=tempdict) + templst.append(json.loads(item[0])) + + return jsonify(latestBlocks=templst), 200 @app.route('/api/v2/blockTransactions/', methods=['GET']) @@ -2252,4 +2254,4 @@ scheduler.start() atexit.register(lambda: scheduler.shutdown()) if __name__ == "__main__": - app.run(debug=debug_status, host='0.0.0.0', port=5009) \ No newline at end of file + app.run(debug=debug_status, host='0.0.0.0', port=5013) From e884e3fafead3019ec9f7dc8598d54ebf74ea6cc Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Fri, 14 Apr 2023 15:38:35 +0000 Subject: [PATCH 16/46] Fixed bug in /api/v2/smartContractList --- ranchimallflo_api.py | 58 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 12 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index adc9092..36c480c 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -1542,11 +1542,16 @@ async def floAddressTransactions(floAddress): @app.route('/api/v2/smartContractList', methods=['GET']) async def getContractList_v2(): contractName = request.args.get('contractName') - contractName = contractName.strip().lower() - contractAddress = request.args.get('contractAddress') + if contractName is not None: + contractName = contractName.strip().lower() + # todo - Add validation for contractAddress and contractName to prevent SQL injection attacks - if contractAddress is not None and not check_flo_address(contractAddress, is_testnet): - return jsonify(description='contractAddress validation failed'), 400 + contractAddress = request.args.get('contractAddress') + if contractAddress is not None: + contractAddress = contractAddress.strip() + if not check_flo_address(contractAddress, is_testnet): + return jsonify(description='contractAddress validation failed'), 400 + contractList = [] conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) @@ -1646,30 +1651,33 @@ async def getcontractparticipants_v2(): token = contractStructure['tokenIdentification'] c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash, winningAmount FROM contractparticipants') result = c.fetchall() - returnval = {} + returnval = [] for row in result: - returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4], 'winningAmount': row[5], 'tokenIdentification': token} + participation = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4], 'winningAmount': row[5], 'tokenIdentification': token} + returnval.append(participation) else: c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash FROM contractparticipants') result = c.fetchall() conn.close() - returnval = {} + returnval = [] for row in result: - returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4]} + participation = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4]} + returnval.append(participation) elif 'payeeAddress' in contractStructure: # contract is of the type internal trigger c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash FROM contractparticipants') result = c.fetchall() conn.close() - returnval = {} + returnval = [] for row in result: - returnval[row[1]] = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'transactionHash': row[4]} + participation = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'transactionHash': row[4]} + returnval.append(participation) elif contractStructure['contractType'] == 'continuos-event' and contractStructure['subtype'] == 'tokenswap': c.execute('SELECT * FROM contractparticipants') contract_participants = c.fetchall() - returnval = {} + returnval = [] for row in contract_participants: - returnval[row[1]] = { + participation = { 'participantFloAddress': row[1], 'participationAmount': row[2], 'swapPrice': float(row[3]), @@ -1678,6 +1686,7 @@ async def getcontractparticipants_v2(): 'blockHash': row[6], 'swapAmount': row[7] } + returnval.append(participation) conn.close() return jsonify(contractName=contractName, contractAddress=contractAddress, participantInfo=returnval), 200 else: @@ -1927,6 +1936,31 @@ async def smartcontracttransactions(): return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404 +@app.route('/api/v2/smartContractDeposits', methods=['GET']) +async def smartcontractdeposits(): + contractName = request.args.get('contractName') + if contractName is None: + return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 + contractName = contractName.strip().lower() + + contractAddress = request.args.get('contractAddress') + if contractAddress is None: + return jsonify(description='Smart Contract\'s address hasn\'t been passed'), 400 + contractAddress = contractAddress.strip() + if not check_flo_address(contractAddress, is_testnet): + return jsonify(description='contractAddress validation failed'), 400 + + contractDbName = '{}-{}.db'.format(contractName, contractAddress) + filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) + + if os.path.isfile(filelocation): + # active deposits + # + return 'complete API' + else: + return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404 + + @app.route('/api/v2/blockDetails/', methods=['GET']) async def blockdetails(blockHash): # todo - validate blockHash From 46ea7ee84fd69278d4d9621d9c13c4c4201fc565 Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Fri, 14 Apr 2023 22:56:25 +0000 Subject: [PATCH 17/46] Further API changes to integrate into floscout --- ranchimallflo_api.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index fc92b01..ffd337a 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -1552,7 +1552,6 @@ async def getContractList_v2(): if not check_flo_address(contractAddress, is_testnet): return jsonify(description='contractAddress validation failed'), 400 - contractList = [] conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) c = conn.cursor() @@ -1602,6 +1601,9 @@ async def getContractInfo_v2(): returnval['totalHonorAmount'] = participation_details[0][2] c.execute('SELECT COUNT(DISTINCT transactionHash) FROM contractdeposits') returnval['numberOfDeposits'] = c.fetchall()[0][0] + # todo - add code to token tracker to save continuos event subtype KEY as contractSubtype as part of contractStructure and remove the following line + returnval['contractSubtype'] = 'tokenswap' + returnval['priceType'] = returnval['pricetype'] elif contractStructure['contractType'] == 'one-time-event' and 'exitconditions' in contractStructure.keys(): returnval['userChoice'] = contractStructure['exitconditions'] returnval.pop('exitconditions') @@ -1616,8 +1618,10 @@ async def getContractInfo_v2(): returnval['expiryDate'] = result[2] if result[3]: returnval['closeDate'] = result[3] + # todo - add code to token tracker to save one-time-event subtype as part of contractStructure and remove the following line + returnval['contractSubtype'] = 'external-trigger' elif contractStructure['contractType'] == 'one-time-event' and 'payeeAddress' in contractStructure.keys(): - pass + returnval['contractSubtype'] = 'time-trigger' return jsonify(contractName=contractName, contractAddress=contractAddress, contractInfo=returnval), 200 else: @@ -1663,6 +1667,7 @@ async def getcontractparticipants_v2(): for row in result: participation = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4]} returnval.append(participation) + return jsonify(contractName=contractName, contractAddress=contractAddress, contractType=contractStructure['contractType'], contractSubtype='external-trigger', participantInfo=returnval), 200 elif 'payeeAddress' in contractStructure: # contract is of the type internal trigger c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash FROM contractparticipants') @@ -1672,6 +1677,7 @@ async def getcontractparticipants_v2(): for row in result: participation = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'transactionHash': row[4]} returnval.append(participation) + return jsonify(contractName=contractName, contractAddress=contractAddress, contractType=contractStructure['contractType'], contractSubtype='time-trigger', participantInfo=returnval), 200 elif contractStructure['contractType'] == 'continuos-event' and contractStructure['subtype'] == 'tokenswap': c.execute('SELECT * FROM contractparticipants') contract_participants = c.fetchall() @@ -1688,7 +1694,7 @@ async def getcontractparticipants_v2(): } returnval.append(participation) conn.close() - return jsonify(contractName=contractName, contractAddress=contractAddress, participantInfo=returnval), 200 + return jsonify(contractName=contractName, contractAddress=contractAddress, contractType=contractStructure['contractType'], contractSubtype=contractStructure['subtype'], participantInfo=returnval), 200 else: return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404 @@ -2288,4 +2294,4 @@ scheduler.start() atexit.register(lambda: scheduler.shutdown()) if __name__ == "__main__": - app.run(debug=debug_status, host='0.0.0.0', port=5013) + app.run(debug=debug_status, host='0.0.0.0', port=5009) From 72aed8673ea7e32037f2124f48a99f03b993bc9f Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Sat, 15 Apr 2023 12:28:54 +0000 Subject: [PATCH 18/46] Logic added to smartContractDeposits API --- ranchimallflo_api.py | 38 ++++++++++++++++++++++++++++++-------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index ffd337a..bbc0a3a 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -1700,7 +1700,7 @@ async def getcontractparticipants_v2(): @app.route('/api/v2/participantDetails/', methods=['GET']) -async def participantDetails(): +async def participantDetails(floAddress): if floAddress is None: return jsonify(description='FLO address hasn\'t been passed'), 400 if not check_flo_address(floAddress, is_testnet): @@ -1942,8 +1942,10 @@ async def smartcontracttransactions(): return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404 -@app.route('/api/v2/smartContractDeposits', methods=['GET']) +# todo - add options to only ask for active/consumed/returned deposits +@app.route('/api/v2/smartContractDeposits/', methods=['GET']) async def smartcontractdeposits(): + # todo - put validation for transactionHash contractName = request.args.get('contractName') if contractName is None: return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 @@ -1955,14 +1957,34 @@ async def smartcontractdeposits(): contractAddress = contractAddress.strip() if not check_flo_address(contractAddress, is_testnet): return jsonify(description='contractAddress validation failed'), 400 - + contractDbName = '{}-{}.db'.format(contractName, contractAddress) filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) - if os.path.isfile(filelocation): # active deposits - # - return 'complete API' + conn = sqlite3.connect(filelocation) + c = conn.cursor() + c.execute('''SELECT depositorAddress, transactionHash, status, depositBalance FROM contractdeposits + WHERE (transactionHash, id) IN (SELECT transactionHash, MAX(id) FROM contractdeposits GROUP BY transactionHash) + ORDER BY id DESC + ;''') + + distinct_deposits = c.fetchall() + deposit_info = [] + for a_deposit in distinct_deposits: + #c.execute(f"SELECT depositBalance FROM contractdeposits WHERE (transactionHash, id) IN (SELECT transactionHash, MIN(id) FROM contractdeposits GROUP BY transactionHash );") + c.execute(f"SELECT depositBalance, unix_expiryTime FROM contractdeposits WHERE transactionHash=='{a_deposit[1]}' ORDER BY id LIMIT 1") + original_deposit_balance = c.fetchall() + obj = { + 'depositorAddress': a_deposit[0], + 'transactionHash': a_deposit[1], + 'status': a_deposit[2], + 'originalBalance': original_deposit_balance[0][0], + 'currentBalance': a_deposit[3], + 'time': original_deposit_balance[0][1] + } + deposit_info.append(obj) + return jsonify(depositInfo=deposit_info), 200 else: return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404 @@ -2155,8 +2177,8 @@ async def categoriseString_v2(urlstring): if urlstring.lower() in onlyfiles: return jsonify(type='token'), 200 else: - contractfolder = os.path.join(dbfolder, 'system.db') - conn = sqlite3.connect(contractfolder) + systemdb = os.path.join(dbfolder, 'system.db') + conn = sqlite3.connect(systemdb) conn.row_factory = lambda cursor, row: row[0] c = conn.cursor() contractList = c.execute('select contractname from activeContracts').fetchall() From 975d85657097d1232df542067ce7ae2af0b93722 Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Mon, 17 Apr 2023 10:09:53 +0000 Subject: [PATCH 19/46] API route fix for smartContractDeposits --- ranchimallflo_api.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index bbc0a3a..7137727 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -1943,7 +1943,7 @@ async def smartcontracttransactions(): # todo - add options to only ask for active/consumed/returned deposits -@app.route('/api/v2/smartContractDeposits/', methods=['GET']) +@app.route('/api/v2/smartContractDeposits', methods=['GET']) async def smartcontractdeposits(): # todo - put validation for transactionHash contractName = request.args.get('contractName') @@ -1966,8 +1966,7 @@ async def smartcontractdeposits(): c = conn.cursor() c.execute('''SELECT depositorAddress, transactionHash, status, depositBalance FROM contractdeposits WHERE (transactionHash, id) IN (SELECT transactionHash, MAX(id) FROM contractdeposits GROUP BY transactionHash) - ORDER BY id DESC - ;''') + ORDER BY id DESC; ''') distinct_deposits = c.fetchall() deposit_info = [] From 0c4b624073c53a181fb4de6829366c3113c85ada Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Sat, 22 Apr 2023 17:28:18 +0000 Subject: [PATCH 20/46] Temp Bug fix for API returning smart contract details fetching from system.db's activecontracts table --- ranchimallflo_api.py | 85 ++++++++++++++++++++++---------------------- 1 file changed, 43 insertions(+), 42 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 7137727..969ebbe 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -99,6 +99,35 @@ def update_transaction_confirmations(transactionJson): transactionJson['confirmations'] = response_data['confirmations'] return transactionJson +def smartcontract_morph_helper(smart_contracts): + contractList = [] + for idx, contract in enumerate(smart_contracts): + contractDict = {} + contractDict['contractName'] = contract[1] + contractDict['contractAddress'] = contract[2] + contractDict['status'] = contract[3] + contractDict['contractType'] = contract[5] + if contractDict['contractType'] == 'continuous-event': + contractDict['contractSubType'] = 'tokenswap' + accepting_selling_tokens = contract[4] + elif contractDict['contractType'] == 'one-time-event': + contractDict['tokenIdentification'] = contract[4] + # pull the contract structure + contractStructure = fetchContractStructure(contractDict['contractName'], contractDict['contractAddress']) + # compare + if 'payeeAddress' in contractStructure.keys(): + contractDict['contractSubType'] = 'time-trigger' + else: + contractDict['contractSubType'] = 'external-trigger' + contractDict['expiryDate'] = contract[9] + contractDict['closeDate'] = contract[10] + + contractDict['transactionHash'] = contract[6] + contractDict['blockNumber'] = contract[7] + contractDict['incorporationDate'] = contract[8] + contractList.append(contractDict) + return contractList + def smartContractInfo_output(contractName, contractAddress, contractType, subtype): if contractType == 'continuos-event' and contractType == 'tokenswap': pass @@ -1387,7 +1416,7 @@ async def floAddressInfo(floAddress): # input validation if not check_flo_address(floAddress, is_testnet): return jsonify(description='floAddress validation failed'), 400 - + dblocation = dbfolder + '/system.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) @@ -1396,7 +1425,7 @@ async def floAddressInfo(floAddress): tokenNames = c.fetchall() c.execute(f"SELECT contractName, status, tokenIdentification, contractType, transactionHash, blockNumber, blockHash FROM activecontracts WHERE contractAddress='{floAddress}'") incorporatedContracts = c.fetchall() - + detailList = None if len(tokenNames) != 0: detailList = {} for token in tokenNames: @@ -1411,11 +1440,12 @@ async def floAddressInfo(floAddress): tempdict['balance'] = balance tempdict['token'] = token detailList[token] = tempdict - else: - # Address is not associated with any token - return jsonify(description='FLO address is not associated with any tokens'), 404 - - if len(incorporatedContracts) != 0: + #else: + # # Address is not associated with any token + # return jsonify(description='FLO address is not associated with any tokens'), 404 + + incorporatedSmartContracts = None + if len(incorporatedContracts) > 0: incorporatedSmartContracts = [] for contract in incorporatedContracts: tempdict = {} @@ -1428,9 +1458,8 @@ async def floAddressInfo(floAddress): tempdict['blockNumber'] = contract[5] tempdict['blockHash'] = contract[6] incorporatedSmartContracts.append(tempdict) - else: - incorporatedContracts=None - return jsonify(floAddress=floAddress, floAddressBalances=detailList, incorporatedSmartContracts=None), 200 + + return jsonify(floAddress=floAddress, floAddressBalances=detailList, incorporatedSmartContracts=incorporatedSmartContracts), 200 @app.route('/api/v2/floAddressBalance/', methods=['GET']) @@ -1556,21 +1585,9 @@ async def getContractList_v2(): conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) c = conn.cursor() smart_contracts = return_smart_contracts(c, contractName, contractAddress) - for idx, contract in enumerate(smart_contracts): - contractDict = {} - contractDict['contractName'] = contract[1] - contractDict['contractAddress'] = contract[2] - contractDict['status'] = contract[3] - contractDict['tokenIdentification'] = contract[4] - contractDict['contractType'] = contract[5] - contractDict['transactionHash'] = contract[6] - contractDict['blockNumber'] = contract[7] - contractDict['incorporationDate'] = contract[8] - contractDict['expiryDate'] = contract[9] - contractDict['closeDate'] = contract[10] - contractList.append(contractDict) + smart_contracts_morphed = smartcontract_morph_helper(smart_contracts) conn.close() - return jsonify(smartContracts=contractList), 200 + return jsonify(smartContracts=smart_contracts_morphed), 200 @app.route('/api/v2/smartContractInfo', methods=['GET']) @@ -2199,26 +2216,10 @@ async def tokenSmartContractList(): # list of smart contracts conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) c = conn.cursor() - contractList = [] 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['tokenIdentification'] = contract[4] - contractDict['contractType'] = contract[5] - contractDict['transactionHash'] = contract[6] - contractDict['blockNumber'] = contract[7] - contractDict['blockHash'] = contract[8] - contractDict['incorporationDate'] = contract[9] - if contract[10]: - contractDict['expiryDate'] = contract[10] - if contract[11]: - contractDict['closeDate'] = contract[11] - contractList.append(contractDict) - return jsonify(tokens=filelist, smartContracts=contractList), 200 + smart_contracts_morphed = smartcontract_morph_helper(allcontractsDetailList) + return jsonify(tokens=filelist, smartContracts=smart_contracts_morphed), 200 class ServerSentEvent: From d17112523dd3fdd7b21ea17e41e2d3e3d57bf447 Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Sat, 22 Apr 2023 17:44:29 +0000 Subject: [PATCH 21/46] Added accepting_token, selling_token info for token swap as part of smart contract info --- ranchimallflo_api.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 969ebbe..58d6289 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -18,6 +18,7 @@ import atexit import pyflo from operator import itemgetter import pdb +import ast app = Quart(__name__) @@ -107,9 +108,11 @@ def smartcontract_morph_helper(smart_contracts): contractDict['contractAddress'] = contract[2] contractDict['status'] = contract[3] contractDict['contractType'] = contract[5] - if contractDict['contractType'] == 'continuous-event': + if contractDict['contractType'] in ['continuous-event', 'continuos-event']: contractDict['contractSubType'] = 'tokenswap' - accepting_selling_tokens = contract[4] + accepting_selling_tokens = ast.literal_eval(contract[4]) + contractDict['acceptingToken'] = accepting_selling_tokens[0] + contractDict['sellingToken'] = accepting_selling_tokens[1] elif contractDict['contractType'] == 'one-time-event': contractDict['tokenIdentification'] = contract[4] # pull the contract structure From 70061a0bec6bab76620e84c107264a47cb5bf2e3 Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Mon, 24 Apr 2023 18:23:59 +0000 Subject: [PATCH 22/46] Show userChoices along with smartContractList functions --- ranchimallflo_api.py | 117 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 1 deletion(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 58d6289..e3e56ec 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -113,6 +113,12 @@ def smartcontract_morph_helper(smart_contracts): accepting_selling_tokens = ast.literal_eval(contract[4]) contractDict['acceptingToken'] = accepting_selling_tokens[0] contractDict['sellingToken'] = accepting_selling_tokens[1] + contractStructure = fetchContractStructure(contractDict['contractName'], contractDict['contractAddress']) + if contractStructure['pricetype'] == 'dynamic': + # temp fix + if 'oracle_address' in contractStructure.keys(): + contractDict['oracle_address'] = contractStructure['oracle_address'] + contractDict['price'] = fetch_dynamic_swap_price(contractStructure, {'time': datetime.now().timestamp()}) elif contractDict['contractType'] == 'one-time-event': contractDict['tokenIdentification'] = contract[4] # pull the contract structure @@ -121,6 +127,10 @@ def smartcontract_morph_helper(smart_contracts): if 'payeeAddress' in contractStructure.keys(): contractDict['contractSubType'] = 'time-trigger' else: + choice_list = [] + for obj_key in contractStructure['exitconditions'].keys(): + choice_list.append(contractStructure['exitconditions'][obj_key]) + contractDict['userChoices'] = choice_list contractDict['contractSubType'] = 'external-trigger' contractDict['expiryDate'] = contract[9] contractDict['closeDate'] = contract[10] @@ -257,6 +267,105 @@ def updatePrices(): c.execute(f"UPDATE ratepairs SET price={pair[1]} WHERE ratepair='{pair[0]}'") conn.commit() +def fetch_dynamic_swap_price(contractStructure, blockinfo): + oracle_address = contractStructure['oracle_address'] + # fetch transactions from the blockchain where from address : oracle-address... to address: contract address + # find the first contract transaction which adheres to price change format + # {"price-update":{"contract-name": "", "contract-address": "", "price": 3}} + print(f'oracle address is : {oracle_address}') + response = requests.get(f'{apiUrl}addr/{oracle_address}') + if response.status_code == 200: + response = response.json() + if 'transactions' not in response.keys(): # API doesn't return 'transactions' key, if 0 txs present on address + return float(contractStructure['price']) + else: + transactions = response['transactions'] + for transaction_hash in transactions: + transaction_response = requests.get(f'{apiUrl}tx/{transaction_hash}') + if transaction_response.status_code == 200: + transaction = transaction_response.json() + floData = transaction['floData'] + # If the blocktime of the transaction is < than the current block time + if transaction['time'] < blockinfo['time']: + # Check if flodata is in the format we are looking for + # ie. {"price-update":{"contract-name": "", "contract-address": "", "price": 3}} + # and receiver address should be contractAddress + try: + sender_address, receiver_address = find_sender_receiver(transaction) + assert receiver_address == contractStructure['contractAddress'] + assert sender_address == oracle_address + floData = json.loads(floData) + # Check if the contract name and address are right + assert floData['price-update']['contract-name'] == contractStructure['contractName'] + assert floData['price-update']['contract-address'] == contractStructure['contractAddress'] + return float(floData['price-update']['price']) + except: + continue + else: + continue + else: + print('API error while fetch_dynamic_swap_price') + return None + return float(contractStructure['price']) + else: + print('API error while fetch_dynamic_swap_price') + return None + +def find_sender_receiver(transaction_data): + # Create vinlist and outputlist + vinlist = [] + querylist = [] + + #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 vin in transaction_data["vin"]: + vinlist.append([vin["addr"], float(vin["value"])]) + + totalinputval = float(transaction_data["valueIn"]) + + # 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: + print(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] + inputadd = vinlist[0][0] + + # todo Rule 42 - If the number of vout is more than 2, reject the transaction + if len(transaction_data["vout"]) > 2: + print(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: + print(f"Transaction's change is not coming back to the input address. Transaction {transaction_data['txid']} is rejected") + return 0 + else: + outputlist = outputlist[0] + + return inputlist[0], outputlist[0] @app.route('/') async def welcome_msg(): @@ -1624,8 +1733,14 @@ async def getContractInfo_v2(): # todo - add code to token tracker to save continuos event subtype KEY as contractSubtype as part of contractStructure and remove the following line returnval['contractSubtype'] = 'tokenswap' returnval['priceType'] = returnval['pricetype'] + returnval['acceptingToken'] = returnval['accepting_token'] + returnval['sellingToken'] = returnval['selling_token'] + returnval['price'] = fetch_dynamic_swap_price(contractStructure, {'time': datetime.now().timestamp()}) elif contractStructure['contractType'] == 'one-time-event' and 'exitconditions' in contractStructure.keys(): - returnval['userChoice'] = contractStructure['exitconditions'] + choice_list = [] + for obj_key in contractStructure['exitconditions'].keys(): + choice_list.append(contractStructure['exitconditions'][obj_key]) + returnval['userChoices'] = choice_list returnval.pop('exitconditions') conn, c = create_database_connection('system_dbs') c.execute('SELECT status, incorporationDate, expiryDate, closeDate FROM activecontracts WHERE contractName=="{}" AND contractAddress=="{}"'.format(contractName, contractAddress)) From 9b50ee8af321895aec7b4834347bb751a6ce7369 Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Sun, 30 Apr 2023 10:59:32 +0000 Subject: [PATCH 23/46] Fixed issue with double smart contracts returns in /api/v2/tokenSmartContractList --- ranchimallflo_api.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index e3e56ec..0e0248b 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -2332,11 +2332,21 @@ async def tokenSmartContractList(): filelist.append(item[:-3]) # list of smart contracts + contractName = request.args.get('contractName') + if contractName is not None: + contractName = contractName.strip().lower() + + # todo - Add validation for contractAddress and contractName to prevent SQL injection attacks + contractAddress = request.args.get('contractAddress') + if contractAddress is not None: + contractAddress = contractAddress.strip() + if not check_flo_address(contractAddress, is_testnet): + return jsonify(description='contractAddress validation failed'), 400 conn = sqlite3.connect(os.path.join(dbfolder, 'system.db')) c = conn.cursor() - c.execute('select * from activecontracts') - allcontractsDetailList = c.fetchall() - smart_contracts_morphed = smartcontract_morph_helper(allcontractsDetailList) + smart_contracts = return_smart_contracts(c, contractName, contractAddress) + smart_contracts_morphed = smartcontract_morph_helper(smart_contracts) + conn.close() return jsonify(tokens=filelist, smartContracts=smart_contracts_morphed), 200 From 71f32d96e4fda503077f4c8400b8df4e0f4c509b Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Sun, 30 Apr 2023 11:02:53 +0000 Subject: [PATCH 24/46] Moved host and port to config file --- ranchimallflo_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 0e0248b..faffb9a 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -2444,4 +2444,4 @@ scheduler.start() atexit.register(lambda: scheduler.shutdown()) if __name__ == "__main__": - app.run(debug=debug_status, host='0.0.0.0', port=5009) + app.run(debug=debug_status, host=HOST, port=PORT) From 646e874d68dcfe66c78e731db1d1c3c90e6acdab Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Sat, 6 May 2023 13:53:46 +0000 Subject: [PATCH 25/46] Fixed bug with status of the contract for one-time-event type of contracts --- ranchimallflo_api.py | 37 +++++++++++++++++++++++++------------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index faffb9a..ab2b24b 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -367,6 +367,12 @@ def find_sender_receiver(transaction_data): return inputlist[0], outputlist[0] +def fetch_contract_status_time_info(contractName, contractAddress): + conn, c = create_database_connection('system_dbs') + c.execute('SELECT status, incorporationDate, expiryDate, closeDate FROM activecontracts WHERE contractName=="{}" AND contractAddress=="{}" ORDER BY id DESC LIMIT 1'.format(contractName, contractAddress)) + contract_status_time_info = c.fetchall() + return contract_status_time_info + @app.route('/') async def welcome_msg(): return jsonify('Welcome to RanchiMall FLO Api v2') @@ -1742,20 +1748,28 @@ async def getContractInfo_v2(): choice_list.append(contractStructure['exitconditions'][obj_key]) returnval['userChoices'] = choice_list returnval.pop('exitconditions') - conn, c = create_database_connection('system_dbs') - c.execute('SELECT status, incorporationDate, expiryDate, closeDate FROM activecontracts WHERE contractName=="{}" AND contractAddress=="{}"'.format(contractName, contractAddress)) - 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] + + contract_status_time_info = fetch_contract_status_time_info(contractName, contractAddress) + if len(contract_status_time_info) == 1: + for status_time_info in contract_status_time_info: + returnval['status'] = status_time_info[0] + returnval['incorporationDate'] = status_time_info[1] + if status_time_info[2]: + returnval['expiryDate'] = status_time_info[2] + if status_time_info[3]: + returnval['closeDate'] = status_time_info[3] # todo - add code to token tracker to save one-time-event subtype as part of contractStructure and remove the following line returnval['contractSubtype'] = 'external-trigger' elif contractStructure['contractType'] == 'one-time-event' and 'payeeAddress' in contractStructure.keys(): + contract_status_time_info = fetch_contract_status_time_info(contractName, contractAddress) + if len(contract_status_time_info) == 1: + for status_time_info in contract_status_time_info: + returnval['status'] = status_time_info[0] + returnval['incorporationDate'] = status_time_info[1] + if status_time_info[2]: + returnval['expiryDate'] = status_time_info[2] + if status_time_info[3]: + returnval['closeDate'] = status_time_info[3] returnval['contractSubtype'] = 'time-trigger' return jsonify(contractName=contractName, contractAddress=contractAddress, contractInfo=returnval), 200 @@ -2417,7 +2431,6 @@ async def priceData(): ####################### ####################### - # if system.db isn't present, initialize it if not os.path.isfile(f"system.db"): # create an empty db From b6f123b15c364eb86cfaa1f235e26c7d500ab203 Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Tue, 16 May 2023 11:16:59 +0000 Subject: [PATCH 26/46] Cleaned redundancy in v2/participantDetails Reffering issue https://github.com/ranchimall/ranchimallflo-api/issues/14 * Removed redundancy in fetching contractStructure * Removed redundncy in connecting to system.db and contract's database --- ranchimallflo_api.py | 84 +++++++++++--------------------------------- 1 file changed, 20 insertions(+), 64 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index ab2b24b..49e07e5 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -1866,11 +1866,11 @@ async def participantDetails(floAddress): return jsonify(description='pass both, contractName and contractAddress as url parameters'), 400 contractName = contractName.strip().lower() - dblocation = os.path.join(dbfolder, 'system.db') - if os.path.isfile(dblocation): + systemdb_location = os.path.join(dbfolder, 'system.db') + if os.path.isfile(systemdb_location): # Make db connection and fetch data - conn = sqlite3.connect(dblocation) - c = conn.cursor() + systemdb_conn = sqlite3.connect(systemdb_location) + c = systemdb_conn.cursor() if contractName is not None: c.execute(f'SELECT * FROM contractAddressMapping WHERE address="{floAddress}" AND addressType="participant" AND contractName="{contractName}" AND contractAddress="{contractAddress}"') else: @@ -1883,35 +1883,23 @@ async def participantDetails(floAddress): detailsDict = {} contract_db = os.path.join(dbfolder, 'smartContracts', f"{contract[3]}-{contract[4]}.db") # Make db connection and fetch contract structure - conn = sqlite3.connect(contract_db) - c = conn.cursor() + contractdb_conn = sqlite3.connect(contract_db) + contract_c = contractdb_conn.cursor() # Get details of the type of Smart Contract - c.execute('SELECT attribute,value FROM contractstructure') - result = c.fetchall() + contract_c.execute('SELECT attribute,value FROM contractstructure') + result = contract_c.fetchall() - contractStructure = {} - conditionDict = {} - counter = 0 - for item in result: - 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 + contractStructure = fetchContractStructure(contract[3], contract[4]) + contractDbName = '{}-{}.db'.format(contract[3], contract[4]) if contractStructure['contractType']=='continuos-event' and contractStructure['subtype']=='tokenswap': # normal result + swap details # what is a api detail - c.execute('SELECT * FROM contractparticipants WHERE participantAddress=?',(floAddress,)) - participant_details = c.fetchall() + contract_c.execute('SELECT * FROM contractparticipants WHERE participantAddress=?',(floAddress,)) + participant_details = contract_c.fetchall() if len(participant_details) > 0: participationList = [] for participation in participant_details: - c.execute("SELECT value FROM contractstructure WHERE attribute='selling_token'") - structure = c.fetchall() detailsDict['participationAddress'] = floAddress detailsDict['participationAmount'] = participation[2] detailsDict['receivedAmount'] = float(participation[3]) @@ -1926,8 +1914,6 @@ async def participantDetails(floAddress): elif contractStructure['contractType']=='one-time-event' and 'payeeAddress' in contractStructure.keys(): # normal results - conn = sqlite3.connect(dblocation) - c = conn.cursor() detailsDict = {} detailsDict['contractName'] = contract[3] detailsDict['contractAddress'] = contract[4] @@ -1948,39 +1934,17 @@ async def participantDetails(floAddress): detailsDict['closeDate'] = temp[0][7] # check if the contract has been closed - contractDbName = '{}-{}.db'.format(detailsDict['contractName'], detailsDict['contractAddress']) 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() - contractStructure = {} - conditionDict = {} - counter = 0 - for item in result: - 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 'payeeAddress' in contractStructure: # contract is of the type external trigger # check if the contract has been closed - c.execute(f"SELECT tokenAmount FROM contractparticipants WHERE participantAddress='{floAddress}'") - result = c.fetchall() - conn.close() + contract_c.execute(f"SELECT tokenAmount FROM contractparticipants WHERE participantAddress='{floAddress}'") + result = contract_c.fetchall() detailsDict['tokenAmount'] = result[0][0] elif contractStructure['contractType']=='one-time-event' and 'exitconditions' in contractStructure.keys(): # normal results + winning/losing details - conn = sqlite3.connect(dblocation) - c = conn.cursor() detailsDict = {} detailsDict['contractName'] = contract[3] detailsDict['contractAddress'] = contract[4] @@ -2001,14 +1965,11 @@ async def participantDetails(floAddress): detailsDict['closeDate'] = temp[0][7] # check if the contract has been closed - contractDbName = '{}-{}.db'.format(detailsDict['contractName'], detailsDict['contractAddress']) 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() + contract_c.execute('SELECT attribute,value FROM contractstructure') + result = contract_c.fetchall() contractStructure = {} conditionDict = {} counter = 0 @@ -2025,19 +1986,14 @@ async def participantDetails(floAddress): if 'exitconditions' in contractStructure: # contract is of the type external trigger # check if the contract has been closed - c.execute('SELECT * FROM contractTransactionHistory WHERE transactionType="trigger"') - trigger = c.fetchall() - if detailsDict['status'] == 'closed': - c.execute(f"SELECT userChoice, winningAmount FROM contractparticipants WHERE participantAddress='{floAddress}'") - result = c.fetchall() - conn.close() + contract_c.execute(f"SELECT userChoice, winningAmount FROM contractparticipants WHERE participantAddress='{floAddress}'") + result = contract_c.fetchall() detailsDict['userChoice'] = result[0][0] detailsDict['winningAmount'] = result[0][1] else: - c.execute(f"SELECT userChoice FROM contractparticipants WHERE participantAddress='{floAddress}'") - result = c.fetchall() - conn.close() + contract_c.execute(f"SELECT userChoice FROM contractparticipants WHERE participantAddress='{floAddress}'") + result = contract_c.fetchall() detailsDict['userChoice'] = result[0][0] participationDetailsList.append(detailsDict) From 84dbe93e811f2d1492d30d373b73e23a1d80ad81 Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Mon, 12 Jun 2023 14:33:43 +0530 Subject: [PATCH 27/46] Added currentDepositBalance in the json response of /api/v2/smartContractInfo --- ranchimallflo_api.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 49e07e5..18a59a3 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -1736,6 +1736,8 @@ async def getContractInfo_v2(): returnval['totalHonorAmount'] = participation_details[0][2] c.execute('SELECT COUNT(DISTINCT transactionHash) FROM contractdeposits') returnval['numberOfDeposits'] = c.fetchall()[0][0] + c.execute('SELECT SUM(depositBalance) AS totalDepositBalance FROM contractdeposits c1 WHERE id = ( SELECT MAX(id) FROM contractdeposits c2 WHERE c1.transactionHash = c2.transactionHash);') + returnval['currentDepositBalance'] = c.fetchall()[0][0] # todo - add code to token tracker to save continuos event subtype KEY as contractSubtype as part of contractStructure and remove the following line returnval['contractSubtype'] = 'tokenswap' returnval['priceType'] = returnval['pricetype'] @@ -2088,7 +2090,11 @@ async def smartcontractdeposits(): 'time': original_deposit_balance[0][1] } deposit_info.append(obj) - return jsonify(depositInfo=deposit_info), 200 + c.execute('''SELECT depositorAddress, transactionHash, status, depositBalance FROM contractdeposits + WHERE (transactionHash, id) IN (SELECT transactionHash, MAX(id) FROM contractdeposits GROUP BY transactionHash) + ORDER BY id DESC; ''') + currentDepositBalance = c.fetchall()[0][0] + return jsonify(currentDepositBalance=currentDepositBalance, depositInfo=deposit_info), 200 else: return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404 From a0f7f78a9ec7481ef0496058e8a1edaf1ec61ea6 Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Mon, 12 Jun 2023 09:12:26 +0000 Subject: [PATCH 28/46] Fix bug in /api/v2/smartContractDeposits --- ranchimallflo_api.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 18a59a3..dded9e3 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -2090,9 +2090,7 @@ async def smartcontractdeposits(): 'time': original_deposit_balance[0][1] } deposit_info.append(obj) - c.execute('''SELECT depositorAddress, transactionHash, status, depositBalance FROM contractdeposits - WHERE (transactionHash, id) IN (SELECT transactionHash, MAX(id) FROM contractdeposits GROUP BY transactionHash) - ORDER BY id DESC; ''') + c.execute('SELECT SUM(depositBalance) AS totalDepositBalance FROM contractdeposits c1 WHERE id = ( SELECT MAX(id) FROM contractdeposits c2 WHERE c1.transactionHash = c2.transactionHash);') currentDepositBalance = c.fetchall()[0][0] return jsonify(currentDepositBalance=currentDepositBalance, depositInfo=deposit_info), 200 else: From 0b78851c97fab28c0eea02b20d67e765f566d1cb Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Fri, 30 Jun 2023 10:05:04 +0000 Subject: [PATCH 29/46] Change in requirements.txt --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 5166ca8..2c9ea24 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -aiofiles==0.4.0 +aiofiles APScheduler==3.9.1 arrow==0.15.2 blinker==1.4 From 1f54180929707dced42370cd4900ca9321209e43 Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Fri, 30 Jun 2023 10:05:59 +0000 Subject: [PATCH 30/46] Change in transaction return format --- ranchimallflo_api.py | 181 ++++++++++++++++++++++++++----------------- 1 file changed, 110 insertions(+), 71 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index dded9e3..6ce358a 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -25,6 +25,9 @@ app = Quart(__name__) app.clients = set() app = cors(app, allow_origin="*") +# Global values and config +internalTransactionTypes = [ 'tokenswapDepositSettlement', 'tokenswapParticipationSettlement'] + if net == 'mainnet': is_testnet = False elif net == 'testnet': @@ -373,6 +376,79 @@ def fetch_contract_status_time_info(contractName, contractAddress): contract_status_time_info = c.fetchall() return contract_status_time_info +def fetch_token_transactions(token, senderFloAddress=None, destFloAddress=None, limit=None): + 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 jsonify(description="Token doesn't exist"), 404 + + # Build the base SQL query + query = 'SELECT jsonData, parsedFloData, time, transactionType, sourceFloAddress, destFloAddress, transferAmount FROM transactionHistory' + + # Build the WHERE clause based on conditions + conditions = [] + parameters = {} + + if senderFloAddress and not destFloAddress: + conditions.append('sourceFloAddress=:sender_flo_address') + parameters['sender_flo_address'] = senderFloAddress + + elif not senderFloAddress and destFloAddress: + conditions.append('destFloAddress=:dest_flo_address') + parameters['dest_flo_address'] = destFloAddress + + elif senderFloAddress and destFloAddress: + conditions.append('sourceFloAddress=:sender_flo_address AND destFloAddress=:dest_flo_address') + parameters['sender_flo_address'] = senderFloAddress + parameters['dest_flo_address'] = destFloAddress + + # Add the WHERE clause if conditions exist + if conditions: + query += ' WHERE {}'.format(' AND '.join(conditions)) + + # Add the LIMIT clause if specified + if limit is not None: + query += ' LIMIT :limit' + parameters['limit'] = limit + + # Execute the query with parameters + c.execute(query, parameters) + transactionJsonData = c.fetchall() + conn.close() + + rowarray_list = [] + for row in transactionJsonData: + transactions_object = {} + parsedFloData = json.loads(row[1]) + transactionDetails = json.loads(row[0]) + if row[3] in internalTransactionTypes: + internal_info = {} + internal_info['senderAddress'] = row[4] + internal_info['receiverAddress'] = row[5] + internal_info['tokenAmount'] = row[6] + internal_info['tokenIdentification'] = token + internal_info['contractName'] = parsedFloData['contractName'] + internal_info['transactionTrigger'] = transactionDetails['txid'] + internal_info['time'] = transactionDetails['time'] + internal_info['type'] = row[3] + internal_info['onChain'] = False + transactions_object = internal_info + else: + transactions_object = {**parsedFloData, **transactionDetails} + transactions_object = update_transaction_confirmations(transactions_object) + transactions_object['onChain'] = True + rowarray_list.append(transactions_object) + return rowarray_list + + +def sort_token_transactions(transactionJsonData): + transactionJsonData = sorted(transactionJsonData, key=lambda x: x['time'], reverse=True) + return transactionJsonData + + @app.route('/') async def welcome_msg(): return jsonify('Welcome to RanchiMall FLO Api v2') @@ -1463,47 +1539,10 @@ async def tokenTransactions(token): limit = request.args.get('limit') if limit is not None and not check_integer(limit): return jsonify(description='limit validation failed'), 400 - - 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 jsonify(description="Token doesn't exist"), 404 - - if senderFloAddress and not destFloAddress: - if limit is None: - c.execute('SELECT jsonData, parsedFloData, time FROM transactionHistory WHERE sourceFloAddress="{}"'.format(senderFloAddress)) - else: - c.execute('SELECT jsonData, parsedFloData, time FROM transactionHistory WHERE sourceFloAddress="{}" LIMIT {}'.format(senderFloAddress, limit)) - elif not senderFloAddress and destFloAddress: - if limit is None: - c.execute('SELECT jsonData, parsedFloData, time FROM transactionHistory WHERE destFloAddress="{}"'.format(destFloAddress)) - else: - c.execute('SELECT jsonData, parsedFloData, time FROM transactionHistory WHERE destFloAddress="{}" LIMIT {}'.format(destFloAddress, limit)) - elif senderFloAddress and destFloAddress: - if limit is None: - c.execute('SELECT jsonData, parsedFloData, time FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}"'.format(senderFloAddress, destFloAddress)) - else: - c.execute('SELECT jsonData, parsedFloData, time FROM transactionHistory WHERE sourceFloAddress="{}" AND destFloAddress="{}" LIMIT {}'.format(senderFloAddress, destFloAddress, limit)) - else: - if limit is None: - c.execute('SELECT jsonData, parsedFloData, time FROM transactionHistory') - else: - c.execute('SELECT jsonData, parsedFloData, time FROM transactionHistory LIMIT {}'.format(limit)) - transactionJsonData = c.fetchall() - conn.close() - - rowarray_list = [] - sorted_list = sorted(transactionJsonData, key=itemgetter('time'), reverse=True) - for row in sorted_list: - transactions_object = {} - transactions_object['transactionDetails'] = json.loads(row[0]) - transactions_object['transactionDetails'] = update_transaction_confirmations(transactions_object['transactionDetails']) - transactions_object['parsedFloData'] = json.loads(row[1]) - rowarray_list.append(transactions_object) - return jsonify(token=token, transactions=rowarray_list), 200 + + transactionJsonData = fetch_token_transactions(token, senderFloAddress, destFloAddress, limit) + sortedFormattedTransactions = sort_token_transactions(transactionJsonData) + return jsonify(token=token, transactions=sortedFormattedTransactions), 200 @app.route('/api/v2/tokenBalances/', methods=['GET']) @@ -1625,7 +1664,7 @@ async def floAddressBalance(floAddress): c.execute(f'SELECT SUM(transferBalance) FROM activeTable WHERE address="{floAddress}"') balance = c.fetchall()[0][0] conn.close() - return jsonify(token=token, floAddress=floAddress, balance=balance), 200 + return jsonify(floAddress=floAddress, token=token, balance=balance), 200 @app.route('/api/v2/floAddressTransactions/', methods=['GET']) @@ -1657,30 +1696,14 @@ async def floAddressTransactions(floAddress): allTransactionList = [] for tokenname in tokenNames: tokenname = tokenname[0] - dblocation = dbfolder + '/tokens/' + str(tokenname) + '.db' - if os.path.exists(dblocation): - conn = sqlite3.connect(dblocation) - conn.row_factory = sqlite3.Row - c = conn.cursor() - if limit is None: - c.execute(f'SELECT jsonData, parsedFloData, time FROM transactionHistory WHERE sourceFloAddress="{floAddress}" OR destFloAddress="{floAddress}" ORDER BY time DESC') - else: - c.execute(f'SELECT jsonData, parsedFloData, time FROM transactionHistory WHERE sourceFloAddress="{floAddress}" OR destFloAddress="{floAddress}" ORDER BY time DESC LIMIT {limit}') - transactionJsonData = c.fetchall() - conn.close() - allTransactionList = allTransactionList + transactionJsonData - rowarray_list = [] - allTransactionList = sorted(allTransactionList, key=itemgetter('time'), reverse=True) - for row in allTransactionList: - tx = {} - tx['transactionDetails'] = json.loads(row[0]) - tx['transactionDetails'] = update_transaction_confirmations(tx['transactionDetails']) - tx['parsedFloData'] = json.loads(row[1]) - rowarray_list.append(tx) + transactionJsonData = fetch_token_transactions(tokenname, limit=limit) + allTransactionList = allTransactionList + transactionJsonData + + sortedFormattedTransactions = sort_token_transactions(allTransactionList) if token is None: - return jsonify(floAddress=floAddress, transactions=rowarray_list), 200 + return jsonify(floAddress=floAddress, transactions=sortedFormattedTransactions), 200 else: - return jsonify(floAddress=floAddress, transactions=rowarray_list, token=token), 200 + return jsonify(floAddress=floAddress, transactions=sortedFormattedTransactions, token=token), 200 else: return jsonify(floAddress=floAddress, transactions=[], token=token), 200 @@ -2043,6 +2066,10 @@ async def smartcontracttransactions(): tx['transactionDetails'] = json.loads(item[0]) tx['transactionDetails'] = update_transaction_confirmations(tx['transactionDetails']) tx['parsedFloData'] = json.loads(item[1]) + + tx = {**tx['transactionDetails'], **tx['parsedFloData']} + # TODO (CRITICAL): Write conditions to include and filter on chain and offchain transactions + tx['onChain'] = True returnval.append(tx) return jsonify(contractName=contractName, contractAddress=contractAddress, contractTransactions=returnval), 200 else: @@ -2182,8 +2209,13 @@ async def transactiondetails1(transactionHash): winningAmount = c.fetchall() if winningAmount[0][0] is not None: operationDetails['winningAmount'] = winningAmount[0][0] - - return jsonify(parsedFloData=parseResult, transactionDetails=transactionJson, transactionHash=transactionHash, operation=operation, operationDetails=operationDetails, senderAddress=sender_address, receiverAddress=receiver_address), 200 + + mergeTx = {**parseResult, **transactionJson} + mergeTx['operation'] = operation + mergeTx['operationDetails'] = operationDetails + # TODO (CRITICAL): Write conditions to include and filter on chain and offchain transactions + mergeTx['onChain'] = True + return jsonify(mergeTx), 200 else: return jsonify(description='Transaction doesn\'t exist in database'), 404 @@ -2216,6 +2248,9 @@ async def latestTransactionDetails(): tx_parsed_details['parsedFloData'] = json.loads(item[5]) tx_parsed_details['parsedFloData']['transactionType'] = item[4] tx_parsed_details['transactionDetails']['blockheight'] = int(item[2]) + tx_parsed_details = {**tx_parsed_details['transactionDetails'], **tx_parsed_details['parsedFloData']} + # TODO (CRITICAL): Write conditions to include and filter on chain and offchain transactions + tx_parsed_details['onChain'] = True tx_list.append(tx_parsed_details) return jsonify(latestTransactions=tx_list), 200 @@ -2258,10 +2293,14 @@ async def blocktransactions(blockHash): temptx = transactiondetailhelper(blocktxlist[i]) transactionJson = json.loads(temptx[0][0]) parseResult = json.loads(temptx[0][1]) - blocktxs[blocktxlist[i]] = { - "parsedFloData" : parseResult, - "transactionDetails" : transactionJson - } + # blocktxs[blocktxlist[i]] = { + # "parsedFloData" : parseResult, + # "transactionDetails" : transactionJson + # } + + blocktxs = {**parseResult , **transactionJson} + # TODO (CRITICAL): Write conditions to include and filter on chain and offchain transactions + blocktxs['onChain'] = True return jsonify(transactions=blocktxs, blockKeyword=blockHash), 200 else: return jsonify(description='Block doesn\'t exist in database'), 404 @@ -2417,4 +2456,4 @@ scheduler.start() atexit.register(lambda: scheduler.shutdown()) if __name__ == "__main__": - app.run(debug=debug_status, host=HOST, port=PORT) + app.run(debug=debug_status, host=HOST, port=PORT) \ No newline at end of file From 5af8a2e1d10eb075d03f558bc3d7e6c9f8fbe164 Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Mon, 3 Jul 2023 19:54:48 +0000 Subject: [PATCH 31/46] Fixed Smart Contract transactions --- ranchimallflo_api.py | 119 ++++++++++++++++++++++++------------------- 1 file changed, 68 insertions(+), 51 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 6ce358a..485579c 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -25,7 +25,7 @@ app = Quart(__name__) app.clients = set() app = cors(app, allow_origin="*") -# Global values and config +# Global values and configg internalTransactionTypes = [ 'tokenswapDepositSettlement', 'tokenswapParticipationSettlement'] if net == 'mainnet': @@ -376,6 +376,31 @@ def fetch_contract_status_time_info(contractName, contractAddress): contract_status_time_info = c.fetchall() return contract_status_time_info +def transaction_post_processing(transactionJsonData): + rowarray_list = [] + for row in transactionJsonData: + transactions_object = {} + parsedFloData = json.loads(row[1]) + transactionDetails = json.loads(row[0]) + if row[3] in internalTransactionTypes: + internal_info = {} + internal_info['senderAddress'] = row[4] + internal_info['receiverAddress'] = row[5] + internal_info['tokenAmount'] = row[6] + internal_info['tokenIdentification'] = row[7] + internal_info['contractName'] = parsedFloData['contractName'] + internal_info['transactionTrigger'] = transactionDetails['txid'] + internal_info['time'] = transactionDetails['time'] + internal_info['type'] = row[3] + internal_info['onChain'] = False + transactions_object = internal_info + else: + transactions_object = {**parsedFloData, **transactionDetails} + transactions_object = update_transaction_confirmations(transactions_object) + transactions_object['onChain'] = True + rowarray_list.append(transactions_object) + return rowarray_list + def fetch_token_transactions(token, senderFloAddress=None, destFloAddress=None, limit=None): dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): @@ -386,7 +411,7 @@ def fetch_token_transactions(token, senderFloAddress=None, destFloAddress=None, return jsonify(description="Token doesn't exist"), 404 # Build the base SQL query - query = 'SELECT jsonData, parsedFloData, time, transactionType, sourceFloAddress, destFloAddress, transferAmount FROM transactionHistory' + query = f"SELECT jsonData, parsedFloData, time, transactionType, sourceFloAddress, destFloAddress, transferAmount, '{token}' AS token FROM transactionHistory" # Build the WHERE clause based on conditions conditions = [] @@ -418,33 +443,37 @@ def fetch_token_transactions(token, senderFloAddress=None, destFloAddress=None, c.execute(query, parameters) transactionJsonData = c.fetchall() conn.close() + return transaction_post_processing(transactionJsonData) - rowarray_list = [] - for row in transactionJsonData: - transactions_object = {} - parsedFloData = json.loads(row[1]) - transactionDetails = json.loads(row[0]) - if row[3] in internalTransactionTypes: - internal_info = {} - internal_info['senderAddress'] = row[4] - internal_info['receiverAddress'] = row[5] - internal_info['tokenAmount'] = row[6] - internal_info['tokenIdentification'] = token - internal_info['contractName'] = parsedFloData['contractName'] - internal_info['transactionTrigger'] = transactionDetails['txid'] - internal_info['time'] = transactionDetails['time'] - internal_info['type'] = row[3] - internal_info['onChain'] = False - transactions_object = internal_info - else: - transactions_object = {**parsedFloData, **transactionDetails} - transactions_object = update_transaction_confirmations(transactions_object) - transactions_object['onChain'] = True - rowarray_list.append(transactions_object) - return rowarray_list +def fetch_contract_transactions(contractName, contractAddress): + sc_file = os.path.join(dbfolder, 'smartContracts', '{}-{}.db'.format(contractName, contractAddress)) + conn = sqlite3.connect(sc_file) + c = conn.cursor() + # Find token db names and attach + contractStructure = fetchContractStructure(contractName, contractAddress) + token1 = contractStructure['accepting_token'] + token2 = contractStructure['selling_token'] + token1_file = f"{dbfolder}/tokens/{token1}.db" + token2_file = f"{dbfolder}/tokens/{token2}.db" + conn.execute(f"ATTACH DATABASE '{token1_file}' AS token1db") + conn.execute(f"ATTACH DATABASE '{token2_file}' AS token2db") + + # Get data from db + c.execute(f''' + SELECT t1.jsonData, t1.parsedFloData, t1.time, t1.transactionType, t1.sourceFloAddress, t1.destFloAddress, t1.transferAmount, '{token1}' AS token + FROM main.contractTransactionHistory AS s + INNER JOIN token1db.transactionHistory AS t1 + ON t1.transactionHash = s.transactionHash + UNION + SELECT t2.jsonData, t2.parsedFloData, t2.time, t2.transactionType, t2.sourceFloAddress, t2.destFloAddress, t2.transferAmount, '{token2}' AS token + FROM main.contractTransactionHistory AS s + INNER JOIN token2db.transactionHistory AS t2 + ON t2.transactionHash = s.transactionHash''') + + transactionJsonData = c.fetchall() + return transaction_post_processing(transactionJsonData) - -def sort_token_transactions(transactionJsonData): +def sort_transactions(transactionJsonData): transactionJsonData = sorted(transactionJsonData, key=lambda x: x['time'], reverse=True) return transactionJsonData @@ -1540,9 +1569,14 @@ async def tokenTransactions(token): if limit is not None and not check_integer(limit): return jsonify(description='limit validation failed'), 400 - transactionJsonData = fetch_token_transactions(token, senderFloAddress, destFloAddress, limit) - sortedFormattedTransactions = sort_token_transactions(transactionJsonData) - return jsonify(token=token, transactions=sortedFormattedTransactions), 200 + filelocation = os.path.join(dbfolder, 'tokens', f'tokens.db') + + if os.path.isfile(filelocation): + transactionJsonData = fetch_token_transactions(token, senderFloAddress, destFloAddress, limit) + sortedFormattedTransactions = sort_transactions(transactionJsonData) + return jsonify(token=token, transactions=sortedFormattedTransactions), 200 + else: + return jsonify(description='Token with the given name doesn\'t exist'), 404 @app.route('/api/v2/tokenBalances/', methods=['GET']) @@ -1699,7 +1733,7 @@ async def floAddressTransactions(floAddress): transactionJsonData = fetch_token_transactions(tokenname, limit=limit) allTransactionList = allTransactionList + transactionJsonData - sortedFormattedTransactions = sort_token_transactions(allTransactionList) + sortedFormattedTransactions = sort_transactions(allTransactionList) if token is None: return jsonify(floAddress=floAddress, transactions=sortedFormattedTransactions), 200 else: @@ -2052,26 +2086,9 @@ async def smartcontracttransactions(): if os.path.isfile(filelocation): # Make db connection and fetch data - conn = sqlite3.connect(filelocation) - c = conn.cursor() - if limit is None: - c.execute('SELECT jsonData, parsedFloData FROM contractTransactionHistory ORDER BY time DESC') - else: - c.execute(f'SELECT jsonData, parsedFloData FROM contractTransactionHistory ORDER BY time DESC LIMIT {limit}') - result = c.fetchall() - conn.close() - returnval = [] - for item in result: - tx = {} - tx['transactionDetails'] = json.loads(item[0]) - tx['transactionDetails'] = update_transaction_confirmations(tx['transactionDetails']) - tx['parsedFloData'] = json.loads(item[1]) - - tx = {**tx['transactionDetails'], **tx['parsedFloData']} - # TODO (CRITICAL): Write conditions to include and filter on chain and offchain transactions - tx['onChain'] = True - returnval.append(tx) - return jsonify(contractName=contractName, contractAddress=contractAddress, contractTransactions=returnval), 200 + transactionJsonData = fetch_contract_transactions(contractName, contractAddress) + transactionJsonData = sort_transactions(transactionJsonData) + return jsonify(contractName=contractName, contractAddress=contractAddress, contractTransactions=transactionJsonData), 200 else: return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404 From 0f6f5fe95537f11564c69f4ccd4158f9a979cb0f Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Mon, 3 Jul 2023 22:43:00 +0000 Subject: [PATCH 32/46] Fix bug : typo --- ranchimallflo_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 485579c..10dca8b 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -1569,7 +1569,7 @@ async def tokenTransactions(token): if limit is not None and not check_integer(limit): return jsonify(description='limit validation failed'), 400 - filelocation = os.path.join(dbfolder, 'tokens', f'tokens.db') + filelocation = os.path.join(dbfolder, 'tokens', f'{token}.db') if os.path.isfile(filelocation): transactionJsonData = fetch_token_transactions(token, senderFloAddress, destFloAddress, limit) From 468d07a0479739728bbc21d5f496305c9173749f Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Tue, 4 Jul 2023 21:43:49 +0000 Subject: [PATCH 33/46] Fixed bug with /api/v2/floAddressTransactions/ Filtering transactions based on senderFloAddress and receiverFloAddress --- ranchimallflo_api.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 10dca8b..8d919de 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -401,7 +401,7 @@ def transaction_post_processing(transactionJsonData): rowarray_list.append(transactions_object) return rowarray_list -def fetch_token_transactions(token, senderFloAddress=None, destFloAddress=None, limit=None): +def fetch_token_transactions(token, senderFloAddress=None, destFloAddress=None, limit=None, use_and=False): dblocation = dbfolder + '/tokens/' + str(token) + '.db' if os.path.exists(dblocation): conn = sqlite3.connect(dblocation) @@ -426,7 +426,10 @@ def fetch_token_transactions(token, senderFloAddress=None, destFloAddress=None, parameters['dest_flo_address'] = destFloAddress elif senderFloAddress and destFloAddress: - conditions.append('sourceFloAddress=:sender_flo_address AND destFloAddress=:dest_flo_address') + if use_and: + conditions.append('sourceFloAddress=:sender_flo_address AND destFloAddress=:dest_flo_address') + else: + conditions.append('sourceFloAddress=:sender_flo_address OR destFloAddress=:dest_flo_address') parameters['sender_flo_address'] = senderFloAddress parameters['dest_flo_address'] = destFloAddress @@ -1568,11 +1571,14 @@ async def tokenTransactions(token): limit = request.args.get('limit') if limit is not None and not check_integer(limit): return jsonify(description='limit validation failed'), 400 + use_AND = request.args.get('use_AND') + if use_AND is not None and use_AND not in [True, False]: + return jsonify(description='use_AND validation failed'), 400 filelocation = os.path.join(dbfolder, 'tokens', f'{token}.db') if os.path.isfile(filelocation): - transactionJsonData = fetch_token_transactions(token, senderFloAddress, destFloAddress, limit) + transactionJsonData = fetch_token_transactions(token, senderFloAddress, destFloAddress, limit, use_AND) sortedFormattedTransactions = sort_transactions(transactionJsonData) return jsonify(token=token, transactions=sortedFormattedTransactions), 200 else: @@ -1730,7 +1736,7 @@ async def floAddressTransactions(floAddress): allTransactionList = [] for tokenname in tokenNames: tokenname = tokenname[0] - transactionJsonData = fetch_token_transactions(tokenname, limit=limit) + transactionJsonData = fetch_token_transactions(tokenname, senderFloAddress=floAddress, destFloAddress=floAddress, limit=limit) allTransactionList = allTransactionList + transactionJsonData sortedFormattedTransactions = sort_transactions(allTransactionList) From d7ef9b456faa4546542d51488459110bc62f1521 Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Wed, 12 Jul 2023 07:21:40 +0000 Subject: [PATCH 34/46] Changes to show sub transactions --- ranchimallflo_api.py | 53 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 8d919de..b1818b9 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -476,6 +476,41 @@ def fetch_contract_transactions(contractName, contractAddress): transactionJsonData = c.fetchall() return transaction_post_processing(transactionJsonData) + +def fetch_contract_transactions_1(contractName, contractAddress, transactionHash=None): + sc_file = os.path.join(dbfolder, 'smartContracts', '{}-{}.db'.format(contractName, contractAddress)) + conn = sqlite3.connect(sc_file) + c = conn.cursor() + # Find token db names and attach + contractStructure = fetchContractStructure(contractName, contractAddress) + token1 = contractStructure['accepting_token'] + token2 = contractStructure['selling_token'] + token1_file = f"{dbfolder}/tokens/{token1}.db" + token2_file = f"{dbfolder}/tokens/{token2}.db" + conn.execute(f"ATTACH DATABASE '{token1_file}' AS token1db") + conn.execute(f"ATTACH DATABASE '{token2_file}' AS token2db") + + # Get data from db + query = f''' + SELECT t1.jsonData, t1.parsedFloData, t1.time, t1.transactionType, t1.sourceFloAddress, t1.destFloAddress, t1.transferAmount, '{token1}' AS token + FROM main.contractTransactionHistory AS s + INNER JOIN token1db.transactionHistory AS t1 + ON t1.transactionHash = s.transactionHash AND s.transactionHash = '{transactionHash}' + UNION + SELECT t2.jsonData, t2.parsedFloData, t2.time, t2.transactionType, t2.sourceFloAddress, t2.destFloAddress, t2.transferAmount, '{token2}' AS token + FROM main.contractTransactionHistory AS s + INNER JOIN token2db.transactionHistory AS t2 + ON t2.transactionHash = s.transactionHash AND s.transactionHash = '{transactionHash}' ''' + + '''if transactionHash: + query += f" WHERE s.transactionHash = '{transactionHash}'"''' + + c.execute(query) + + transactionJsonData = c.fetchall() + return transaction_post_processing(transactionJsonData) + + def sort_transactions(transactionJsonData): transactionJsonData = sorted(transactionJsonData, key=lambda x: x['time'], reverse=True) return transactionJsonData @@ -2162,6 +2197,7 @@ async def blockdetails(blockHash): async def transactiondetails1(transactionHash): # todo - validate transactionHash transactionJsonData = transactiondetailhelper(transactionHash) + if len(transactionJsonData) != 0: transactionJson = json.loads(transactionJsonData[0][0]) transactionJson = update_transaction_confirmations(transactionJson) @@ -2169,7 +2205,12 @@ async def transactiondetails1(transactionHash): operation = transactionJsonData[0][2] db_reference = transactionJsonData[0][3] sender_address, receiver_address = extract_ip_op_addresses(transactionJson) + contractName, contractAddress = db_reference.rsplit('-',1) + mergeTx = {**parseResult, **transactionJson} + # TODO (CRITICAL): Write conditions to include and filter on chain and offchain transactions + mergeTx['onChain'] = True + operationDetails = {} if operation == 'smartContractDeposit': # open the db reference and check if there is a deposit return @@ -2233,11 +2274,17 @@ async def transactiondetails1(transactionHash): if winningAmount[0][0] is not None: operationDetails['winningAmount'] = winningAmount[0][0] - mergeTx = {**parseResult, **transactionJson} + elif operation == 'tokenswapParticipation': + conn = sqlite3.connect(f"{dbfolder}/smartContracts/{db_reference}.db") + c = conn.cursor() + txhash_txs = fetch_contract_transactions_1(contractName, contractAddress, transactionHash) + mergeTx['subtxs'] = [] + for transaction in txhash_txs: + if transaction['onChain'] == False: + mergeTx['subtxs'].append(transaction) + mergeTx['operation'] = operation mergeTx['operationDetails'] = operationDetails - # TODO (CRITICAL): Write conditions to include and filter on chain and offchain transactions - mergeTx['onChain'] = True return jsonify(mergeTx), 200 else: return jsonify(description='Transaction doesn\'t exist in database'), 404 From 57f5aae06bb7219783134fed81d3c5a601263bce Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Wed, 12 Jul 2023 08:07:02 +0000 Subject: [PATCH 35/46] Changing key from subtxs to subTransactions --- ranchimallflo_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index b1818b9..e7fe50d 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -2278,10 +2278,10 @@ async def transactiondetails1(transactionHash): conn = sqlite3.connect(f"{dbfolder}/smartContracts/{db_reference}.db") c = conn.cursor() txhash_txs = fetch_contract_transactions_1(contractName, contractAddress, transactionHash) - mergeTx['subtxs'] = [] + mergeTx['subTransactions'] = [] for transaction in txhash_txs: if transaction['onChain'] == False: - mergeTx['subtxs'].append(transaction) + mergeTx['subTransactions'].append(transaction) mergeTx['operation'] = operation mergeTx['operationDetails'] = operationDetails From 60adde47688c443272f84747501a9c4cb652dbcf Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Thu, 13 Jul 2023 12:11:54 +0000 Subject: [PATCH 36/46] Bugfix for transactionDetails --- ranchimallflo_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index e7fe50d..a495fdf 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -2205,11 +2205,10 @@ async def transactiondetails1(transactionHash): operation = transactionJsonData[0][2] db_reference = transactionJsonData[0][3] sender_address, receiver_address = extract_ip_op_addresses(transactionJson) - contractName, contractAddress = db_reference.rsplit('-',1) mergeTx = {**parseResult, **transactionJson} # TODO (CRITICAL): Write conditions to include and filter on chain and offchain transactions - mergeTx['onChain'] = True + mergeTx['onChain'] = True operationDetails = {} if operation == 'smartContractDeposit': @@ -2275,6 +2274,7 @@ async def transactiondetails1(transactionHash): operationDetails['winningAmount'] = winningAmount[0][0] elif operation == 'tokenswapParticipation': + contractName, contractAddress = db_reference.rsplit('-',1) conn = sqlite3.connect(f"{dbfolder}/smartContracts/{db_reference}.db") c = conn.cursor() txhash_txs = fetch_contract_transactions_1(contractName, contractAddress, transactionHash) From af78189f9bf73aaceba801b0a45570d3a877558a Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Fri, 25 Aug 2023 07:04:16 +0000 Subject: [PATCH 37/46] Fixed bug in /api/v2/smartContractTransactions - Contract Creation transactions are showing up on --- ranchimallflo_api.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index a495fdf..8861161 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -472,8 +472,17 @@ def fetch_contract_transactions(contractName, contractAddress): FROM main.contractTransactionHistory AS s INNER JOIN token2db.transactionHistory AS t2 ON t2.transactionHash = s.transactionHash''') - - transactionJsonData = c.fetchall() + + transactionJsonData = c.fetchall() + + c.execute(f''' + SELECT jsonData, parsedFloData, time, transactionType, sourceFloAddress, destFloAddress, transferAmount, '' AS token + FROM contractTransactionHistory + ORDER BY id + LIMIT 1; + ''') + creation_tx = c.fetchall() + transactionJsonData = creation_tx + transactionJsonData return transaction_post_processing(transactionJsonData) From 7f0d7797a4aa8737436a69b8b201b80e02827312 Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Wed, 30 Aug 2023 18:00:03 +0000 Subject: [PATCH 38/46] Added python .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index ffa4bf3..7950ab2 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ py3.8/ *.db *.code-workspace *.log +py*/ From b1c3851564c262610e36624f15f545971f2c6115 Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Wed, 30 Aug 2023 18:29:37 +0000 Subject: [PATCH 39/46] Changes to accomodate blockbook transaciton format --- ranchimallflo_api.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 8861161..6a55e45 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -231,7 +231,7 @@ def fetchContractStatus(contractName, contractAddress): return status[0][0] def extract_ip_op_addresses(transactionJson): - sender_address = transactionJson['vin'][0]['addr'] + sender_address = transactionJson['vin'][0]['addresses'][0] receiver_address = None for utxo in transactionJson['vout']: if utxo['scriptPubKey']['addresses'][0] == sender_address: @@ -2119,7 +2119,6 @@ async def smartcontracttransactions(): if contractName is None: return jsonify(description='Smart Contract\'s name hasn\'t been passed'), 400 contractName = contractName.strip().lower() - contractAddress = request.args.get('contractAddress') if contractAddress is None: return jsonify(description='Smart Contract\'s address hasn\'t been passed'), 400 @@ -2366,10 +2365,10 @@ async def blocktransactions(blockHash): blockJson = blockdetailhelper(blockHash) if len(blockJson) != 0: blockJson = json.loads(blockJson[0][0]) - blocktxlist = blockJson['tx'] - blocktxs = {} + blocktxlist = blockJson['txs'] + blocktxs = [] for i in range(len(blocktxlist)): - temptx = transactiondetailhelper(blocktxlist[i]) + temptx = transactiondetailhelper(blocktxlist[i]['txid']) transactionJson = json.loads(temptx[0][0]) parseResult = json.loads(temptx[0][1]) # blocktxs[blocktxlist[i]] = { @@ -2377,7 +2376,7 @@ async def blocktransactions(blockHash): # "transactionDetails" : transactionJson # } - blocktxs = {**parseResult , **transactionJson} + blocktxs.append({**parseResult , **transactionJson}) # TODO (CRITICAL): Write conditions to include and filter on chain and offchain transactions blocktxs['onChain'] = True return jsonify(transactions=blocktxs, blockKeyword=blockHash), 200 From 3629ff6cea28ad148d920d668c03f999eac5a993 Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Sun, 24 Sep 2023 11:23:05 +0000 Subject: [PATCH 40/46] Fixed bug with token transactions --- ranchimallflo_api.py | 113 +++++++++++++++++++++++++++---------------- 1 file changed, 71 insertions(+), 42 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 6a55e45..be7a56c 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -26,7 +26,7 @@ app.clients = set() app = cors(app, allow_origin="*") # Global values and configg -internalTransactionTypes = [ 'tokenswapDepositSettlement', 'tokenswapParticipationSettlement'] +internalTransactionTypes = [ 'tokenswapDepositSettlement', 'tokenswapParticipationSettlement', 'smartContractDepositReturn'] if net == 'mainnet': is_testnet = False @@ -91,7 +91,7 @@ def transactiondetailhelper(transactionHash): # open the latest block database conn = sqlite3.connect(os.path.join(dbfolder, 'latestCache.db')) c = conn.cursor() - c.execute(f"select jsonData, parsedFloData, transactionType, db_reference from latestTransactions where transactionHash='{transactionHash}'") + c.execute(f"SELECT jsonData, parsedFloData, transactionType, db_reference FROM latestTransactions WHERE transactionHash='{transactionHash}'") transactionJsonData = c.fetchall() return transactionJsonData @@ -376,13 +376,19 @@ def fetch_contract_status_time_info(contractName, contractAddress): contract_status_time_info = c.fetchall() return contract_status_time_info +def checkIF_commitee_trigger_tranasaction(transactionDetails): + if transactionDetails[3] == 'trigger': + pass + def transaction_post_processing(transactionJsonData): rowarray_list = [] + for row in transactionJsonData: transactions_object = {} parsedFloData = json.loads(row[1]) transactionDetails = json.loads(row[0]) - if row[3] in internalTransactionTypes: + + if row[3] in internalTransactionTypes or (row[3]=='trigger' and row[8]!='committee'): internal_info = {} internal_info['senderAddress'] = row[4] internal_info['receiverAddress'] = row[5] @@ -398,7 +404,9 @@ def transaction_post_processing(transactionJsonData): transactions_object = {**parsedFloData, **transactionDetails} transactions_object = update_transaction_confirmations(transactions_object) transactions_object['onChain'] = True + rowarray_list.append(transactions_object) + return rowarray_list def fetch_token_transactions(token, senderFloAddress=None, destFloAddress=None, limit=None, use_and=False): @@ -411,7 +419,7 @@ def fetch_token_transactions(token, senderFloAddress=None, destFloAddress=None, return jsonify(description="Token doesn't exist"), 404 # Build the base SQL query - query = f"SELECT jsonData, parsedFloData, time, transactionType, sourceFloAddress, destFloAddress, transferAmount, '{token}' AS token FROM transactionHistory" + query = f"SELECT jsonData, parsedFloData, time, transactionType, sourceFloAddress, destFloAddress, transferAmount, '{token}' AS token, '' AS transactionSubType FROM transactionHistory" # Build the WHERE clause based on conditions conditions = [] @@ -454,39 +462,62 @@ def fetch_contract_transactions(contractName, contractAddress): c = conn.cursor() # Find token db names and attach contractStructure = fetchContractStructure(contractName, contractAddress) - token1 = contractStructure['accepting_token'] - token2 = contractStructure['selling_token'] - token1_file = f"{dbfolder}/tokens/{token1}.db" - token2_file = f"{dbfolder}/tokens/{token2}.db" - conn.execute(f"ATTACH DATABASE '{token1_file}' AS token1db") - conn.execute(f"ATTACH DATABASE '{token2_file}' AS token2db") + + if contractStructure['contractType'] == 'continuos-event': + token1 = contractStructure['accepting_token'] + token2 = contractStructure['selling_token'] + token1_file = f"{dbfolder}/tokens/{token1}.db" + token2_file = f"{dbfolder}/tokens/{token2}.db" + conn.execute(f"ATTACH DATABASE '{token1_file}' AS token1db") + conn.execute(f"ATTACH DATABASE '{token2_file}' AS token2db") + # Get data from db + c.execute(f''' + SELECT t1.jsonData, t1.parsedFloData, t1.time, t1.transactionType, t1.sourceFloAddress, t1.destFloAddress, t1.transferAmount, '{token1}' AS token, s.transactionSubType + FROM main.contractTransactionHistory AS s + INNER JOIN token1db.transactionHistory AS t1 + ON t1.transactionHash = s.transactionHash + UNION + SELECT t2.jsonData, t2.parsedFloData, t2.time, t2.transactionType, t2.sourceFloAddress, t2.destFloAddress, t2.transferAmount, '{token2}' AS token, s.transactionSubType + FROM main.contractTransactionHistory AS s + INNER JOIN token2db.transactionHistory AS t2 + ON t2.transactionHash = s.transactionHash''') + + transactionJsonData = c.fetchall() + c.execute(f''' + SELECT jsonData, parsedFloData, time, transactionType, sourceFloAddress, destFloAddress, transferAmount, '' AS token, transactionSubType + FROM contractTransactionHistory + ORDER BY id + LIMIT 1; + ''') + creation_tx = c.fetchall() + transactionJsonData = creation_tx + transactionJsonData - # Get data from db - c.execute(f''' - SELECT t1.jsonData, t1.parsedFloData, t1.time, t1.transactionType, t1.sourceFloAddress, t1.destFloAddress, t1.transferAmount, '{token1}' AS token - FROM main.contractTransactionHistory AS s - INNER JOIN token1db.transactionHistory AS t1 - ON t1.transactionHash = s.transactionHash - UNION - SELECT t2.jsonData, t2.parsedFloData, t2.time, t2.transactionType, t2.sourceFloAddress, t2.destFloAddress, t2.transferAmount, '{token2}' AS token - FROM main.contractTransactionHistory AS s - INNER JOIN token2db.transactionHistory AS t2 - ON t2.transactionHash = s.transactionHash''') + elif contractStructure['contractType'] == 'one-time-event': + #if contractStructure['subtype'] == 'time-trigger' and contractStructure['subtype'] == 'external-trigger': + token1 = contractStructure['tokenIdentification'] + token1_file = f"{dbfolder}/tokens/{token1}.db" + conn.execute(f"ATTACH DATABASE '{token1_file}' AS token1db") + # Get data from db + c.execute(f''' + SELECT t1.jsonData, t1.parsedFloData, t1.time, t1.transactionType, t1.sourceFloAddress, t1.destFloAddress, t1.transferAmount, '{token1}' AS token, s.transactionSubType + FROM main.contractTransactionHistory AS s + INNER JOIN token1db.transactionHistory AS t1 + ON t1.transactionHash = s.transactionHash''') - transactionJsonData = c.fetchall() - - c.execute(f''' - SELECT jsonData, parsedFloData, time, transactionType, sourceFloAddress, destFloAddress, transferAmount, '' AS token - FROM contractTransactionHistory - ORDER BY id - LIMIT 1; - ''') - creation_tx = c.fetchall() - transactionJsonData = creation_tx + transactionJsonData + transactionJsonData = c.fetchall() + c.execute(f''' + SELECT jsonData, parsedFloData, time, transactionType, sourceFloAddress, destFloAddress, transferAmount, '' AS token, transactionSubType + FROM contractTransactionHistory + ORDER BY id + LIMIT 1; + ''') + creation_tx = c.fetchall() + transactionJsonData = creation_tx + transactionJsonData + return transaction_post_processing(transactionJsonData) -def fetch_contract_transactions_1(contractName, contractAddress, transactionHash=None): +def fetch_swap_contract_transactions(contractName, contractAddress, transactionHash=None): sc_file = os.path.join(dbfolder, 'smartContracts', '{}-{}.db'.format(contractName, contractAddress)) conn = sqlite3.connect(sc_file) c = conn.cursor() @@ -501,12 +532,12 @@ def fetch_contract_transactions_1(contractName, contractAddress, transactionHash # Get data from db query = f''' - SELECT t1.jsonData, t1.parsedFloData, t1.time, t1.transactionType, t1.sourceFloAddress, t1.destFloAddress, t1.transferAmount, '{token1}' AS token + SELECT t1.jsonData, t1.parsedFloData, t1.time, t1.transactionType, t1.sourceFloAddress, t1.destFloAddress, t1.transferAmount, '{token1}' AS token, t1.transactionSubType FROM main.contractTransactionHistory AS s INNER JOIN token1db.transactionHistory AS t1 ON t1.transactionHash = s.transactionHash AND s.transactionHash = '{transactionHash}' UNION - SELECT t2.jsonData, t2.parsedFloData, t2.time, t2.transactionType, t2.sourceFloAddress, t2.destFloAddress, t2.transferAmount, '{token2}' AS token + SELECT t2.jsonData, t2.parsedFloData, t2.time, t2.transactionType, t2.sourceFloAddress, t2.destFloAddress, t2.transferAmount, '{token2}' AS token, t2.transactionSubType FROM main.contractTransactionHistory AS s INNER JOIN token2db.transactionHistory AS t2 ON t2.transactionHash = s.transactionHash AND s.transactionHash = '{transactionHash}' ''' @@ -1848,9 +1879,11 @@ async def getContractInfo_v2(): # todo - add code to token tracker to save continuos event subtype KEY as contractSubtype as part of contractStructure and remove the following line returnval['contractSubtype'] = 'tokenswap' returnval['priceType'] = returnval['pricetype'] + if returnval['pricetype'] not in ['predetermined']: + returnval['price'] = fetch_dynamic_swap_price(contractStructure, {'time': datetime.now().timestamp()}) returnval['acceptingToken'] = returnval['accepting_token'] returnval['sellingToken'] = returnval['selling_token'] - returnval['price'] = fetch_dynamic_swap_price(contractStructure, {'time': datetime.now().timestamp()}) + elif contractStructure['contractType'] == 'one-time-event' and 'exitconditions' in contractStructure.keys(): choice_list = [] for obj_key in contractStructure['exitconditions'].keys(): @@ -2285,7 +2318,7 @@ async def transactiondetails1(transactionHash): contractName, contractAddress = db_reference.rsplit('-',1) conn = sqlite3.connect(f"{dbfolder}/smartContracts/{db_reference}.db") c = conn.cursor() - txhash_txs = fetch_contract_transactions_1(contractName, contractAddress, transactionHash) + txhash_txs = fetch_swap_contract_transactions(contractName, contractAddress, transactionHash) mergeTx['subTransactions'] = [] for transaction in txhash_txs: if transaction['onChain'] == False: @@ -2371,14 +2404,10 @@ async def blocktransactions(blockHash): temptx = transactiondetailhelper(blocktxlist[i]['txid']) transactionJson = json.loads(temptx[0][0]) parseResult = json.loads(temptx[0][1]) - # blocktxs[blocktxlist[i]] = { - # "parsedFloData" : parseResult, - # "transactionDetails" : transactionJson - # } - blocktxs.append({**parseResult , **transactionJson}) + # TODO (CRITICAL): Write conditions to include and filter on chain and offchain transactions - blocktxs['onChain'] = True + #blocktxs['onChain'] = True return jsonify(transactions=blocktxs, blockKeyword=blockHash), 200 else: return jsonify(description='Block doesn\'t exist in database'), 404 From 7dd859ec5e88db3c228003d7a496328ca23e1f3d Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Fri, 29 Sep 2023 12:03:47 +0000 Subject: [PATCH 41/46] Fixed issues with participant winning amount --- ranchimallflo_api.py | 49 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index be7a56c..2a0f9f4 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -516,7 +516,6 @@ def fetch_contract_transactions(contractName, contractAddress): return transaction_post_processing(transactionJsonData) - def fetch_swap_contract_transactions(contractName, contractAddress, transactionHash=None): sc_file = os.path.join(dbfolder, 'smartContracts', '{}-{}.db'.format(contractName, contractAddress)) conn = sqlite3.connect(sc_file) @@ -550,11 +549,48 @@ def fetch_swap_contract_transactions(contractName, contractAddress, transactionH transactionJsonData = c.fetchall() return transaction_post_processing(transactionJsonData) - def sort_transactions(transactionJsonData): transactionJsonData = sorted(transactionJsonData, key=lambda x: x['time'], reverse=True) return transactionJsonData +def refresh_committee_list(admin_flo_id, api_url, blocktime): + committee_list = [] + latest_param = 'true' + mempool_param = 'false' + init_id = None + + def process_transaction(transaction_info): + if 'isCoinBase' in transaction_info or transaction_info['vin'][0]['addresses'][0] != admin_flo_id or transaction_info['blocktime'] > blocktime: + return + try: + tx_flodata = json.loads(transaction_info['floData']) + committee_list.extend(process_committee_flodata(tx_flodata)) + except: + pass + + def send_api_request(url): + response = requests.get(url, verify=API_VERIFY) + if response.status_code == 200: + return response.json() + else: + print('Response from the Flosight API failed') + sys.exit(0) + + url = f'{api_url}api/v1/address/{admin_flo_id}?details=txs' + response = send_api_request(url) + for transaction_info in response.get('txs', []): + process_transaction(transaction_info) + + while 'incomplete' in response: + url = f'{api_url}api/v1/address/{admin_flo_id}/txs?latest={latest_param}&mempool={mempool_param}&before={init_id}' + response = send_api_request(url) + for transaction_info in response.get('items', []): + process_transaction(transaction_info) + if 'incomplete' in response: + init_id = response['initItem'] + + return committee_list + @app.route('/') async def welcome_msg(): @@ -1948,7 +1984,14 @@ async def getcontractparticipants_v2(): result = c.fetchall() returnval = [] for row in result: - participation = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4], 'winningAmount': row[5], 'tokenIdentification': token} + # Check value of winning amount + c.execute(f'SELECT winningAmount FROM contractwinners WHERE referenceTxHash="{row[4]}"') + participant_winningAmount = c.fetchall() + if participant_winningAmount != []: + participant_winningAmount = participant_winningAmount[0][0] + else: + participant_winningAmount = 0 + participation = {'participantFloAddress': row[1], 'tokenAmount': row[2], 'userChoice': row[3], 'transactionHash': row[4], 'winningAmount': participant_winningAmount, 'tokenIdentification': token} returnval.append(participation) else: c.execute('SELECT id, participantAddress, tokenAmount, userChoice, transactionHash FROM contractparticipants') From 5dfb75417f9784dd8a7c40347d9f07a02eebf3c8 Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Tue, 14 Nov 2023 06:48:38 +0000 Subject: [PATCH 42/46] smartContractList and tokenSmartContractList returns committee address --- ranchimallflo_api.py | 60 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 12 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 2a0f9f4..8d9ca8c 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -19,6 +19,7 @@ import pyflo from operator import itemgetter import pdb import ast +import time app = Quart(__name__) @@ -96,7 +97,7 @@ def transactiondetailhelper(transactionHash): return transactionJsonData def update_transaction_confirmations(transactionJson): - url = f"{apiUrl}tx/{transactionJson['txid']}" + url = f"{apiUrl}api/v1/tx/{transactionJson['txid']}" response = requests.get(url) if response.status_code == 200: response_data = response.json() @@ -122,6 +123,8 @@ def smartcontract_morph_helper(smart_contracts): if 'oracle_address' in contractStructure.keys(): contractDict['oracle_address'] = contractStructure['oracle_address'] contractDict['price'] = fetch_dynamic_swap_price(contractStructure, {'time': datetime.now().timestamp()}) + else: + contractDict['price'] = contractStructure['price'] elif contractDict['contractType'] == 'one-time-event': contractDict['tokenIdentification'] = contract[4] # pull the contract structure @@ -276,7 +279,7 @@ def fetch_dynamic_swap_price(contractStructure, blockinfo): # find the first contract transaction which adheres to price change format # {"price-update":{"contract-name": "", "contract-address": "", "price": 3}} print(f'oracle address is : {oracle_address}') - response = requests.get(f'{apiUrl}addr/{oracle_address}') + response = requests.get(f'{apiUrl}api/v1/addr/{oracle_address}') if response.status_code == 200: response = response.json() if 'transactions' not in response.keys(): # API doesn't return 'transactions' key, if 0 txs present on address @@ -284,7 +287,7 @@ def fetch_dynamic_swap_price(contractStructure, blockinfo): else: transactions = response['transactions'] for transaction_hash in transactions: - transaction_response = requests.get(f'{apiUrl}tx/{transaction_hash}') + transaction_response = requests.get(f'{apiUrl}api/v1/tx/{transaction_hash}') if transaction_response.status_code == 200: transaction = transaction_response.json() floData = transaction['floData'] @@ -544,7 +547,10 @@ def fetch_swap_contract_transactions(contractName, contractAddress, transactionH '''if transactionHash: query += f" WHERE s.transactionHash = '{transactionHash}'"''' - c.execute(query) + try: + c.execute(query) + except: + pass transactionJsonData = c.fetchall() return transaction_post_processing(transactionJsonData) @@ -553,6 +559,27 @@ def sort_transactions(transactionJsonData): transactionJsonData = sorted(transactionJsonData, key=lambda x: x['time'], reverse=True) return transactionJsonData +def process_committee_flodata(flodata): + flo_address_list = [] + try: + contract_committee_actions = flodata['token-tracker']['contract-committee'] + except KeyError: + print('Flodata related to contract committee') + else: + # Adding first and removing later to maintain consistency and not to depend on floData for order of execution + for action in contract_committee_actions.keys(): + if action == 'add': + for floid in contract_committee_actions[f'{action}']: + flo_address_list.append(floid) + + for action in contract_committee_actions.keys(): + if action == 'remove': + for floid in contract_committee_actions[f'{action}']: + flo_address_list.remove(floid) + finally: + return flo_address_list + + def refresh_committee_list(admin_flo_id, api_url, blocktime): committee_list = [] latest_param = 'true' @@ -823,8 +850,7 @@ async def getAddressBalance(): tempdict = {} conn = sqlite3.connect(dblocation) c = conn.cursor() - c.execute( - 'SELECT SUM(transferBalance) FROM activeTable WHERE address="{}"'.format(floAddress)) + c.execute('SELECT SUM(transferBalance) FROM activeTable WHERE address="{}"'.format(floAddress)) balance = c.fetchall()[0][0] tempdict['balance'] = balance tempdict['token'] = token @@ -1527,11 +1553,11 @@ async def getblocktransactions(blockdetail): @app.route('/api/v1.0/categoriseString/') async def categoriseString(urlstring): # check if the hash is of a transaction - response = requests.get('{}tx/{}'.format(apiUrl, urlstring)) + response = requests.get('{}api/v1/tx/{}'.format(apiUrl, urlstring)) if response.status_code == 200: return jsonify(type='transaction') else: - response = requests.get('{}block/{}'.format(apiUrl, urlstring)) + response = requests.get('{}api/v1/block/{}'.format(apiUrl, urlstring)) if response.status_code == 200: return jsonify(type='block') else: @@ -1879,7 +1905,10 @@ async def getContractList_v2(): smart_contracts = return_smart_contracts(c, contractName, contractAddress) smart_contracts_morphed = smartcontract_morph_helper(smart_contracts) conn.close() - return jsonify(smartContracts=smart_contracts_morphed), 200 + + committeeAddressList = refresh_committee_list(APP_ADMIN, apiUrl, int(time.time())) + + return jsonify(smartContracts=smart_contracts_morphed, smartContractCommittee=committeeAddressList), 200 @app.route('/api/v2/smartContractInfo', methods=['GET']) @@ -1903,8 +1932,13 @@ async def getContractInfo_v2(): # Categorize into what type of contract it is right now if contractStructure['contractType'] == 'continuos-event' and contractStructure['subtype'] == 'tokenswap': conn, c = create_database_connection('smart_contract', {'contract_name': contractName, 'contract_address': contractAddress}) + c.execute('SELECT COUNT(participantAddress), SUM(tokenAmount), SUM(winningAmount) FROM contractparticipants') participation_details = c.fetchall() + + c.execute('SELECT depositAmount FROM contractdeposits') + deposit_details = c.fetchall() + returnval['numberOfParticipants'] = participation_details[0][0] returnval['totalParticipationAmount'] = participation_details[0][1] returnval['totalHonorAmount'] = participation_details[0][2] @@ -2459,11 +2493,11 @@ async def blocktransactions(blockHash): @app.route('/api/v2/categoriseString/') async def categoriseString_v2(urlstring): # check if the hash is of a transaction - response = requests.get('{}tx/{}'.format(apiUrl, urlstring)) + response = requests.get('{}api/v1/tx/{}'.format(apiUrl, urlstring)) if response.status_code == 200: return jsonify(type='transaction'), 200 else: - response = requests.get('{}block/{}'.format(apiUrl, urlstring)) + response = requests.get('{}api/v1/block/{}'.format(apiUrl, urlstring)) if response.status_code == 200: return jsonify(type='block'), 200 else: @@ -2510,7 +2544,9 @@ async def tokenSmartContractList(): smart_contracts = return_smart_contracts(c, contractName, contractAddress) smart_contracts_morphed = smartcontract_morph_helper(smart_contracts) conn.close() - return jsonify(tokens=filelist, smartContracts=smart_contracts_morphed), 200 + + committeeAddressList = refresh_committee_list(APP_ADMIN, apiUrl, int(time.time())) + return jsonify(tokens=filelist, smartContracts=smart_contracts_morphed, smartContractCommittee=committeeAddressList), 200 class ServerSentEvent: From a9f691f82fa21b24667612a9b2a8b1e61e75f80a Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Wed, 22 Nov 2023 18:57:32 +0000 Subject: [PATCH 43/46] Added pagination to contract transactions --- ranchimallflo_api.py | 85 ++++++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 35 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index 8d9ca8c..bf6c062 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -21,7 +21,6 @@ import pdb import ast import time - app = Quart(__name__) app.clients = set() app = cors(app, allow_origin="*") @@ -459,7 +458,7 @@ def fetch_token_transactions(token, senderFloAddress=None, destFloAddress=None, conn.close() return transaction_post_processing(transactionJsonData) -def fetch_contract_transactions(contractName, contractAddress): +def fetch_contract_transactions(contractName, contractAddress, _from=0, to=100): sc_file = os.path.join(dbfolder, 'smartContracts', '{}-{}.db'.format(contractName, contractAddress)) conn = sqlite3.connect(sc_file) c = conn.cursor() @@ -473,8 +472,8 @@ def fetch_contract_transactions(contractName, contractAddress): token2_file = f"{dbfolder}/tokens/{token2}.db" conn.execute(f"ATTACH DATABASE '{token1_file}' AS token1db") conn.execute(f"ATTACH DATABASE '{token2_file}' AS token2db") - # Get data from db - c.execute(f''' + + transaction_query = f''' SELECT t1.jsonData, t1.parsedFloData, t1.time, t1.transactionType, t1.sourceFloAddress, t1.destFloAddress, t1.transferAmount, '{token1}' AS token, s.transactionSubType FROM main.contractTransactionHistory AS s INNER JOIN token1db.transactionHistory AS t1 @@ -483,42 +482,47 @@ def fetch_contract_transactions(contractName, contractAddress): SELECT t2.jsonData, t2.parsedFloData, t2.time, t2.transactionType, t2.sourceFloAddress, t2.destFloAddress, t2.transferAmount, '{token2}' AS token, s.transactionSubType FROM main.contractTransactionHistory AS s INNER JOIN token2db.transactionHistory AS t2 - ON t2.transactionHash = s.transactionHash''') + ON t2.transactionHash = s.transactionHash + WHERE s.id BETWEEN {_from} AND {to} + ''' + + creation_tx_query = ''' + SELECT jsonData, parsedFloData, time, transactionType, sourceFloAddress, destFloAddress, transferAmount, '' AS token, transactionSubType + FROM contractTransactionHistory + ORDER BY id + LIMIT 1; + ''' - transactionJsonData = c.fetchall() - c.execute(f''' - SELECT jsonData, parsedFloData, time, transactionType, sourceFloAddress, destFloAddress, transferAmount, '' AS token, transactionSubType - FROM contractTransactionHistory - ORDER BY id - LIMIT 1; - ''') - creation_tx = c.fetchall() - transactionJsonData = creation_tx + transactionJsonData - elif contractStructure['contractType'] == 'one-time-event': - #if contractStructure['subtype'] == 'time-trigger' and contractStructure['subtype'] == 'external-trigger': token1 = contractStructure['tokenIdentification'] token1_file = f"{dbfolder}/tokens/{token1}.db" conn.execute(f"ATTACH DATABASE '{token1_file}' AS token1db") - # Get data from db - c.execute(f''' + + transaction_query = f''' SELECT t1.jsonData, t1.parsedFloData, t1.time, t1.transactionType, t1.sourceFloAddress, t1.destFloAddress, t1.transferAmount, '{token1}' AS token, s.transactionSubType FROM main.contractTransactionHistory AS s INNER JOIN token1db.transactionHistory AS t1 - ON t1.transactionHash = s.transactionHash''') + ON t1.transactionHash = s.transactionHash + WHERE s.id BETWEEN {_from} AND {to} + ''' + + creation_tx_query = ''' + SELECT jsonData, parsedFloData, time, transactionType, sourceFloAddress, destFloAddress, transferAmount, '' AS token, transactionSubType + FROM contractTransactionHistory + ORDER BY id + LIMIT 1; + ''' + + c.execute(transaction_query) + transactionJsonData = c.fetchall() + + c.execute(creation_tx_query) + creation_tx = c.fetchall() + transactionJsonData = creation_tx + transactionJsonData - transactionJsonData = c.fetchall() - c.execute(f''' - SELECT jsonData, parsedFloData, time, transactionType, sourceFloAddress, destFloAddress, transferAmount, '' AS token, transactionSubType - FROM contractTransactionHistory - ORDER BY id - LIMIT 1; - ''') - creation_tx = c.fetchall() - transactionJsonData = creation_tx + transactionJsonData - return transaction_post_processing(transactionJsonData) + def fetch_swap_contract_transactions(contractName, contractAddress, transactionHash=None): sc_file = os.path.join(dbfolder, 'smartContracts', '{}-{}.db'.format(contractName, contractAddress)) conn = sqlite3.connect(sc_file) @@ -1647,7 +1651,6 @@ async def broadcastTx_v2(raw_transaction_hash): # FLO TOKEN APIs - @app.route('/api/v2/tokenList', methods=['GET']) async def tokenList(): filelist = [] @@ -1712,6 +1715,14 @@ async def tokenTransactions(token): if use_AND is not None and use_AND not in [True, False]: return jsonify(description='use_AND validation failed'), 400 + _from = int(request.args.get('_from', 1)) # Get page number, default is 1 + to = int(request.args.get('to', 100)) # Get limit, default is 10 + + if _from<1: + return jsonify(description='_from validation failed'), 400 + if to<1: + return jsonify(description='to validation failed'), 400 + filelocation = os.path.join(dbfolder, 'tokens', f'{token}.db') if os.path.isfile(filelocation): @@ -2008,7 +2019,7 @@ async def getcontractparticipants_v2(): # Make db connection and fetch data contractStructure = fetchContractStructure(contractName, contractAddress) contractStatus = fetchContractStatus(contractName, contractAddress) - conn, c = create_database_connection('smart_contract', {'contract_name': contractName, 'contract_address': contractAddress}) + conn, c = create_database_connection('smart_contract', {'contract_name': contractName, 'contract_address': contractAddress}) if 'exitconditions' in contractStructure: # contract is of the type external trigger # check if the contract has been closed @@ -2236,16 +2247,20 @@ async def smartcontracttransactions(): if not check_flo_address(contractAddress, is_testnet): return jsonify(description='contractAddress validation failed'), 400 - limit = request.args.get('limit') - if limit is not None and not check_integer(limit): - return jsonify(description='limit validation failed'), 400 + _from = int(request.args.get('_from', 1)) # Get page number, default is 1 + to = int(request.args.get('to', 100)) # Get limit, default is 10 + + if _from<1: + return jsonify(description='_from validation failed'), 400 + if to<1: + return jsonify(description='to validation failed'), 400 contractDbName = '{}-{}.db'.format(contractName, contractAddress) filelocation = os.path.join(dbfolder, 'smartContracts', contractDbName) if os.path.isfile(filelocation): # Make db connection and fetch data - transactionJsonData = fetch_contract_transactions(contractName, contractAddress) + transactionJsonData = fetch_contract_transactions(contractName, contractAddress, _from, to) transactionJsonData = sort_transactions(transactionJsonData) return jsonify(contractName=contractName, contractAddress=contractAddress, contractTransactions=transactionJsonData), 200 else: From 0eb93ad916be044dc0d8a18b9d9bb25cd5c027a7 Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Sat, 10 Feb 2024 06:49:06 +0000 Subject: [PATCH 44/46] Error handling 1. Fixed error handling in /api/v2/participantDetails The case where contractAddress nor contractName was passed is handled now 2. Fixed error handling in /api/v2/broadcastTx/ Datadir parameter which is unnecesary for the cli command is removed --- ranchimallflo_api.py | 38 ++++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index bf6c062..db903ca 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -1646,7 +1646,7 @@ async def info(): @app.route('/api/v2/broadcastTx/') async def broadcastTx_v2(raw_transaction_hash): - p1 = subprocess.run(['flo-cli',f"-datadir={FLO_DATA_DIR}",'sendrawtransaction',raw_transaction_hash], capture_output=True) + p1 = subprocess.run(['flo-cli','sendrawtransaction',raw_transaction_hash], capture_output=True) return jsonify(args=p1.args,returncode=p1.returncode,stdout=p1.stdout.decode(),stderr=p1.stderr.decode()), 200 @@ -2063,14 +2063,14 @@ async def getcontractparticipants_v2(): returnval = [] for row in contract_participants: participation = { - 'participantFloAddress': row[1], - 'participationAmount': row[2], - 'swapPrice': float(row[3]), - 'transactionHash': row[4], - 'blockNumber': row[5], - 'blockHash': row[6], - 'swapAmount': row[7] - } + 'participantFloAddress': row[1], + 'participationAmount': row[2], + 'swapPrice': float(row[3]), + 'transactionHash': row[4], + 'blockNumber': row[5], + 'blockHash': row[6], + 'swapAmount': row[7] + } returnval.append(participation) conn.close() return jsonify(contractName=contractName, contractAddress=contractAddress, contractType=contractStructure['contractType'], contractSubtype=contractStructure['subtype'], participantInfo=returnval), 200 @@ -2087,14 +2087,20 @@ async def participantDetails(floAddress): contractName = request.args.get('contractName') contractAddress = request.args.get('contractAddress') - - if contractAddress is not None and not check_flo_address(contractAddress, is_testnet): - return jsonify(description='contractAddress validation failed'), 400 - contractAddress = contractAddress.strip() - if (contractName and contractAddress is None) or (contractName is None and contractAddress): - return jsonify(description='pass both, contractName and contractAddress as url parameters'), 400 + # Url param checking + if contractName is None and contractAddress is None: + return jsonify(description='Pass both, contractName and contractAddress as url parameters'), 400 + elif contractName is None: + return jsonify(description='Pass contractName as url parameter'), 400 + else: + return jsonify(description='Pass contractAddress as url parameter'), 400 + + if not check_flo_address(contractAddress, is_testnet): + return jsonify(description='contractAddress validation failed'), 400 + contractName = contractName.strip().lower() + contractAddress = contractAddress.strip() systemdb_location = os.path.join(dbfolder, 'system.db') if os.path.isfile(systemdb_location): @@ -2657,4 +2663,4 @@ scheduler.start() atexit.register(lambda: scheduler.shutdown()) if __name__ == "__main__": - app.run(debug=debug_status, host=HOST, port=PORT) \ No newline at end of file + app.run(debug=debug_status, host=HOST, port=PORT) From 49a3295225e3426256f590215f16dcf5186eb001 Mon Sep 17 00:00:00 2001 From: RanchiMall Dev Date: Wed, 6 Mar 2024 10:37:10 +0000 Subject: [PATCH 45/46] Fix bug for swap contract dynamic price calculation caused by change to blockbook API The recent update to the external API introduced a change in the response format, resulting in a bug in our code. This commit adjusts the handling of API responses to accommodate the new format, resolving the issue and ensuring proper functionality. --- ranchimallflo_api.py | 57 ++++++++++++++++++++++---------------------- 1 file changed, 28 insertions(+), 29 deletions(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index db903ca..f49683f 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -278,39 +278,38 @@ def fetch_dynamic_swap_price(contractStructure, blockinfo): # find the first contract transaction which adheres to price change format # {"price-update":{"contract-name": "", "contract-address": "", "price": 3}} print(f'oracle address is : {oracle_address}') - response = requests.get(f'{apiUrl}api/v1/addr/{oracle_address}') + response = requests.get(f'{apiUrl}api/v1/address/{oracle_address}?details=txs') if response.status_code == 200: response = response.json() - if 'transactions' not in response.keys(): # API doesn't return 'transactions' key, if 0 txs present on address + if 'txs' not in response.keys(): # API doesn't return 'transactions' key, if 0 txs present on address return float(contractStructure['price']) else: - transactions = response['transactions'] - for transaction_hash in transactions: - transaction_response = requests.get(f'{apiUrl}api/v1/tx/{transaction_hash}') - if transaction_response.status_code == 200: - transaction = transaction_response.json() - floData = transaction['floData'] - # If the blocktime of the transaction is < than the current block time - if transaction['time'] < blockinfo['time']: - # Check if flodata is in the format we are looking for - # ie. {"price-update":{"contract-name": "", "contract-address": "", "price": 3}} - # and receiver address should be contractAddress - try: - sender_address, receiver_address = find_sender_receiver(transaction) - assert receiver_address == contractStructure['contractAddress'] - assert sender_address == oracle_address - floData = json.loads(floData) - # Check if the contract name and address are right - assert floData['price-update']['contract-name'] == contractStructure['contractName'] - assert floData['price-update']['contract-address'] == contractStructure['contractAddress'] - return float(floData['price-update']['price']) - except: - continue - else: + transactions = response['txs'] + for transaction in transactions: + #transaction_response = requests.get(f'{apiUrl}api/v1/tx/{transaction_hash}') + # if transaction_response.status_code == 200: + floData = transaction['floData'] + # If the blocktime of the transaction is < than the current block time + if transaction['time'] < blockinfo['time']: + # Check if flodata is in the format we are looking for + # ie. {"price-update":{"contract-name": "", "contract-address": "", "price": 3}} + # and receiver address should be contractAddress + try: + sender_address, receiver_address = find_sender_receiver(transaction) + assert receiver_address == contractStructure['contractAddress'] + assert sender_address == oracle_address + floData = json.loads(floData) + # Check if the contract name and address are right + assert floData['price-update']['contract-name'] == contractStructure['contractName'] + assert floData['price-update']['contract-address'] == contractStructure['contractAddress'] + return float(floData['price-update']['price']) + except: continue else: - print('API error while fetch_dynamic_swap_price') - return None + continue + # else: + # print('API error while fetch_dynamic_swap_price') + # return None return float(contractStructure['price']) else: print('API error while fetch_dynamic_swap_price') @@ -326,7 +325,7 @@ def find_sender_receiver(transaction_data): # todo Rule 40 - For each vin, find the feeding address and the fed value. Make an inputlist containing [inputaddress, n value] for vin in transaction_data["vin"]: - vinlist.append([vin["addr"], float(vin["value"])]) + vinlist.append([vin["addresses"][0], float(vin["value"])]) totalinputval = float(transaction_data["valueIn"]) @@ -355,7 +354,7 @@ def find_sender_receiver(transaction_data): addresscounter = 0 inputcounter = 0 for obj in transaction_data["vout"]: - if obj["scriptPubKey"]["type"] == "pubkeyhash": + if obj["scriptPubKey"]["isAddress"] == True: addresscounter = addresscounter + 1 if inputlist[0] == obj["scriptPubKey"]["addresses"][0]: inputcounter = inputcounter + 1 From bb717d9b29aeda827e3966cb9fe7b128ccea9ec9 Mon Sep 17 00:00:00 2001 From: Vivek Teega Date: Wed, 18 Sep 2024 13:52:17 +0530 Subject: [PATCH 46/46] Changed the price update API interval --- ranchimallflo_api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ranchimallflo_api.py b/ranchimallflo_api.py index f49683f..ff00957 100644 --- a/ranchimallflo_api.py +++ b/ranchimallflo_api.py @@ -2656,7 +2656,7 @@ if not os.path.isfile(f"system.db"): # assign a scheduler for updating prices in the background scheduler = BackgroundScheduler() -scheduler.add_job(func=updatePrices, trigger="interval", seconds=600) +scheduler.add_job(func=updatePrices, trigger="interval", seconds=7200) scheduler.start() # Shut down the scheduler when exiting the app atexit.register(lambda: scheduler.shutdown())