Adding flags to indicate status of backend

- API responses now have a warning message if backend is still syncing
- API v2 responses now have HTTP codes 200, 206, 400, 404, 500, 503
200: response is ok
206: reponse is ok, but backend is still syncing, thus data might not be final
404: data not found error
503: data not found, but backend is still syncing
400: client request error
500: internal server error
This commit is contained in:
Sai Raj 2024-08-12 21:09:08 -04:00
parent 63e5a0da87
commit a71c15fe6c
4 changed files with 365 additions and 104 deletions

View File

@ -4,6 +4,7 @@ import threading
from src.api.api_main import start_api_server
from src.backend.backend_main import start_backend_process
import config as config
from flags import set_run_start
DELAY_API_SERVER_START = 60 # 1 min
@ -18,6 +19,7 @@ if __name__ == "__main__":
# parse the config file into dict
_config = convert_to_dict(config)
set_run_start()
# start the backend process (token scanner). pass reset=True if --reset is in command-line args
if "--reset" in sys.argv or "-r" in sys.argv:

View File

@ -19,12 +19,15 @@ from operator import itemgetter
import pdb
import ast
import time
from src.flags import is_backend_ready
app = Quart(__name__)
app.clients = set()
app = cors(app, allow_origin="*")
INTERNAL_ERROR = "Unable to process request, try again later"
BACKEND_NOT_READY_ERROR = "Server is still syncing, try again later!"
BACKEND_NOT_READY_WARNING = "Server is still syncing, data may not be final"
# Global values and configg
internalTransactionTypes = [ 'tokenswapDepositSettlement', 'tokenswapParticipationSettlement', 'smartContractDepositReturn']
@ -675,6 +678,8 @@ async def broadcastTx(raw_transaction_hash):
# FLO TOKEN APIs
@app.route('/api/v1.0/getTokenList', methods=['GET'])
async def getTokenList():
if not is_backend_ready():
return jsonify(result='error', description=BACKEND_NOT_READY_ERROR)
try:
filelist = []
for item in os.listdir(os.path.join(DATA_PATH, 'tokens')):
@ -698,7 +703,10 @@ async def getTokenInfo():
conn = sqlite3.connect(dblocation)
c = conn.cursor()
else:
return jsonify(result='error', description='token doesn\'t exist')
if not is_backend_ready():
return jsonify(result='error', description=BACKEND_NOT_READY_ERROR)
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')
@ -719,8 +727,11 @@ async def getTokenInfo():
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)
if not is_backend_ready():
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, warning=BACKEND_NOT_READY_WARNING)
else:
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)
except Exception as e:
print("getTokenInfo:", e)
@ -744,7 +755,10 @@ async def getTokenTransactions():
conn.row_factory = sqlite3.Row
c = conn.cursor()
else:
return jsonify(result='error', description='token doesn\'t exist')
if not is_backend_ready():
return jsonify(result='error', description=BACKEND_NOT_READY_ERROR)
else:
return jsonify(result='error', description='token doesn\'t exist')
if senderFloAddress and not destFloAddress:
if limit is None:
@ -776,7 +790,10 @@ async def getTokenTransactions():
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)
if not is_backend_ready():
return jsonify(result='ok', token=token, transactions=rowarray_list, warning=BACKEND_NOT_READY_WARNING)
else:
return jsonify(result='ok', token=token, transactions=rowarray_list)
except Exception as e:
print("getTokenTransactions:", e)
@ -795,7 +812,10 @@ async def getTokenBalances():
conn = sqlite3.connect(dblocation)
c = conn.cursor()
else:
return jsonify(result='error', description='token doesn\'t exist')
if not is_backend_ready():
return jsonify(result='error', description=BACKEND_NOT_READY_ERROR)
else:
return jsonify(result='error', description='token doesn\'t exist')
c.execute('SELECT address,SUM(transferBalance) FROM activeTable GROUP BY address')
addressBalances = c.fetchall()
@ -803,8 +823,12 @@ async def getTokenBalances():
for address in addressBalances:
returnList[address[0]] = address[1]
if not is_backend_ready():
return jsonify(result='ok', token=token, balances=returnList, warning=BACKEND_NOT_READY_WARNING)
else:
return jsonify(result='ok', token=token, balances=returnList)
return jsonify(result='ok', token=token, balances=returnList)
except Exception as e:
print("getTokenBalances:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
@ -844,7 +868,10 @@ async def getFloAddressInfo():
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 not is_backend_ready():
return jsonify(result='error', description=BACKEND_NOT_READY_ERROR)
else:
return jsonify(result='error', description='FLO address is not associated with any tokens')
if len(incorporatedContracts) != 0:
incorporatedSmartContracts = []
@ -859,10 +886,13 @@ async def getFloAddressInfo():
tempdict['blockNumber'] = contract[5]
tempdict['blockHash'] = contract[6]
incorporatedSmartContracts.append(tempdict)
else:
incorporatedSmartContracts = None
if not is_backend_ready():
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', warning=BACKEND_NOT_READY_WARNING, floAddress=floAddress, floAddressBalances=detailList, incorporatedSmartContracts=incorporatedContracts)
except Exception as e:
print("getFloAddressInfo:", e)
@ -901,24 +931,35 @@ async def getAddressBalance():
tempdict['balance'] = balance
tempdict['token'] = token
detailList[token] = tempdict
return jsonify(result='ok', floAddress=floAddress, floAddressBalances=detailList)
if not is_backend_ready():
return jsonify(result='ok', warning=BACKEND_NOT_READY_WARNING, floAddress=floAddress, floAddressBalances=detailList)
else:
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')
if not is_backend_ready():
return jsonify(result='error', description=BACKEND_NOT_READY_ERROR)
else:
return jsonify(result='error', description='FLO address is not associated with any tokens')
else:
dblocation = DATA_PATH + '/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')
if not is_backend_ready():
return jsonify(result='error', description=BACKEND_NOT_READY_ERROR)
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)
if not is_backend_ready():
jsonify(result='ok', warning=BACKEND_NOT_READY_WARNING, token=token, floAddress=floAddress, balance=balance)
else:
return jsonify(result='ok', token=token, floAddress=floAddress, balance=balance)
except Exception as e:
print("getAddressBalance:", e)
@ -947,7 +988,10 @@ async def getFloAddressTransactions():
if os.path.exists(dblocation):
tokenNames = [[str(token), ]]
else:
return jsonify(result='error', description='token doesn\'t exist')
if not is_backend_ready():
return jsonify(result='error', description=BACKEND_NOT_READY_ERROR)
else:
return jsonify(result='error', description='token doesn\'t exist')
if len(tokenNames) != 0:
allTransactionList = {}
@ -973,11 +1017,20 @@ async def getFloAddressTransactions():
allTransactionList[transactions_object['transactionDetails']['txid']] = transactions_object
if token is None:
return jsonify(result='ok', floAddress=floAddress, transactions=allTransactionList)
if not is_backend_ready():
return jsonify(result='ok', warning=BACKEND_NOT_READY_WARNING, floAddress=floAddress, transactions=allTransactionList)
else:
return jsonify(result='ok', floAddress=floAddress, transactions=allTransactionList)
else:
return jsonify(result='ok', floAddress=floAddress, transactions=allTransactionList, token=token)
if not is_backend_ready():
return jsonify(result='ok', warning=BACKEND_NOT_READY_WARNING, floAddress=floAddress, transactions=allTransactionList, token=token)
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')
if not is_backend_ready():
return jsonify(result='error', description=BACKEND_NOT_READY_ERROR)
else:
return jsonify(result='error', description='No token transactions present present on this address')
except Exception as e:
print("getFloAddressTransactions:", e)
@ -1076,7 +1129,10 @@ async def getContractList():
contractList.append(contractDict)
return jsonify(smartContracts=contractList, result='ok')
if not is_backend_ready():
return jsonify(smartContracts=contractList, result='ok', warning=BACKEND_NOT_READY_WARNING)
else:
return jsonify(smartContracts=contractList, result='ok')
except Exception as e:
@ -1177,11 +1233,16 @@ async def getContractInfo():
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)
if not is_backend_ready():
return jsonify(result='ok', warning=BACKEND_NOT_READY_WARNING, contractName=contractName, contractAddress=contractAddress, contractInfo=returnval)
else:
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')
if not is_backend_ready():
return jsonify(result='error', description=BACKEND_NOT_READY_ERROR)
else:
return jsonify(result='error', details='Smart Contract with the given name doesn\'t exist')
except Exception as e:
print("getContractInfo:", e)
@ -1264,10 +1325,15 @@ async def getcontractparticipants():
'swapAmount': row[7]
}
conn.close()
return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, participantInfo=returnval)
if not is_backend_ready():
return jsonify(result='ok', warning=BACKEND_NOT_READY_WARNING, contractName=contractName, contractAddress=contractAddress, participantInfo=returnval)
else:
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')
if not is_backend_ready():
return jsonify(result='error', description=BACKEND_NOT_READY_ERROR)
else:
return jsonify(result='error', description='Smart Contract with the given name doesn\'t exist')
except Exception as e:
print("getcontractparticipants:", e)
@ -1469,10 +1535,16 @@ async def getParticipantDetails():
participationDetailsList.append(detailsDict)
return jsonify(result='ok', floAddress=floAddress, type='participant', participatedContracts=participationDetailsList)
if not is_backend_ready():
return jsonify(result='ok', warning=BACKEND_NOT_READY_WARNING, floAddress=floAddress, type='participant', participatedContracts=participationDetailsList)
else:
return jsonify(result='ok', floAddress=floAddress, type='participant', participatedContracts=participationDetailsList)
else:
return jsonify(result='error', description='Address hasn\'t participated in any other contract')
if not is_backend_ready():
return jsonify(result='error', description=BACKEND_NOT_READY_ERROR)
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')
@ -1512,10 +1584,16 @@ async def getsmartcontracttransactions():
transactions_object['parsedFloData'] = json.loads(item[1])
returnval[transactions_object['transactionDetails']['txid']] = transactions_object
return jsonify(result='ok', contractName=contractName, contractAddress=contractAddress, contractTransactions=returnval)
if not is_backend_ready():
return jsonify(result='ok', warning=BACKEND_NOT_READY_WARNING, contractName=contractName, contractAddress=contractAddress, contractTransactions=returnval)
else:
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')
if not is_backend_ready():
return jsonify(result='error', description=BACKEND_NOT_READY_ERROR)
else:
return jsonify(result='error', description='Smart Contract with the given name doesn\'t exist')
except Exception as e:
print("getParticipantDetails:", e)
@ -1530,7 +1608,10 @@ async def getblockdetails(blockdetail):
blockJson = json.loads(blockJson[0][0])
return jsonify(result='ok', blockDetails=blockJson)
else:
return jsonify(result='error', description='Block doesn\'t exist in database')
if not is_backend_ready():
return jsonify(result='error', description=BACKEND_NOT_READY_ERROR)
else:
return jsonify(result='error', description='Block doesn\'t exist in database')
except Exception as e:
print("getblockdetails:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
@ -1547,7 +1628,10 @@ async def gettransactiondetails(transactionHash):
return jsonify(parsedFloData=parseResult, transactionDetails=transactionJson, transactionHash=transactionHash, result='ok')
else:
return jsonify(result='error', description='Transaction doesn\'t exist in database')
if not is_backend_ready():
return jsonify(result='error', description=BACKEND_NOT_READY_ERROR)
else:
return jsonify(result='error', description='Transaction doesn\'t exist in database')
except Exception as e:
print("gettransactiondetails:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
@ -1594,7 +1678,10 @@ async def getLatestTransactionDetails():
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)
if not is_backend_ready():
return jsonify(result='ok', warning=BACKEND_NOT_READY_WARNING, latestTransactions=tempdict)
else:
return jsonify(result='ok', latestTransactions=tempdict)
except Exception as e:
print("getLatestTransactionDetails:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
@ -1622,7 +1709,10 @@ async def getLatestBlockDetails():
tempdict = {}
for idx, item in enumerate(latestBlocks):
tempdict[json.loads(item[3])['hash']] = json.loads(item[3])
return jsonify(result='ok', latestBlocks=tempdict)
if not is_backend_ready():
return jsonify(result='ok', warning=BACKEND_NOT_READY_WARNING, latestBlocks=tempdict)
else:
return jsonify(result='ok', latestBlocks=tempdict)
except Exception as e:
print("getsmartcontracttransactions:", e)
@ -1648,7 +1738,10 @@ async def getblocktransactions(blockdetail):
}
return jsonify(result='ok', transactions=blocktxs, blockKeyword=blockdetail)
else:
return jsonify(result='error', description='Block doesn\'t exist in database')
if not is_backend_ready():
return jsonify(result='error', description=BACKEND_NOT_READY_ERROR)
else:
return jsonify(result='error', description='Block doesn\'t exist in database')
except Exception as e:
@ -1723,7 +1816,10 @@ async def getTokenSmartContractList():
contractDict['closeDate'] = contract[11]
contractList.append(contractDict)
return jsonify(tokens=filelist, smartContracts=contractList, result='ok')
if not is_backend_ready():
return jsonify(tokens=filelist, warning=BACKEND_NOT_READY_WARNING, smartContracts=contractList, result='ok')
else:
return jsonify(tokens=filelist, smartContracts=contractList, result='ok')
except Exception as e:
print("getTokenSmartContractList:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
@ -1752,12 +1848,14 @@ async def info():
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
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, systemAddressCount=tokenAddressCount, systemBlockCount=validatedBlockCount, systemTransactionCount=validatedTransactionCount, systemSmartContractCount=contractCount, systemTokenCount=tokenCount, lastscannedblock=lastscannedblock), 206
else:
return jsonify(systemAddressCount=tokenAddressCount, systemBlockCount=validatedBlockCount, systemTransactionCount=validatedTransactionCount, systemSmartContractCount=contractCount, systemTokenCount=tokenCount, lastscannedblock=lastscannedblock), 200
except Exception as e:
print("info:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
@app.route('/api/v2/broadcastTx/<raw_transaction_hash>')
@ -1769,7 +1867,7 @@ async def broadcastTx_v2(raw_transaction_hash):
except Exception as e:
print("broadcastTx_v2:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
# FLO TOKEN APIs
@app.route('/api/v2/tokenList', methods=['GET'])
@ -1779,10 +1877,13 @@ async def tokenList():
for item in os.listdir(os.path.join(DATA_PATH, 'tokens')):
if os.path.isfile(os.path.join(DATA_PATH, 'tokens', item)):
filelist.append(item[:-3])
return jsonify(tokens=filelist), 200
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, tokens=filelist), 206
else:
return jsonify(tokens=filelist), 200
except Exception as e:
print("tokenList:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
@ -1798,7 +1899,10 @@ async def tokenInfo(token):
conn = sqlite3.connect(dblocation)
c = conn.cursor()
else:
return jsonify(description="Token doesn't exist"), 404
if not is_backend_ready():
return jsonify(description=BACKEND_NOT_READY_ERROR), 503
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')
@ -1820,11 +1924,14 @@ async def tokenInfo(token):
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
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, token=token, incorporationAddress=incorporationRow[1], tokenSupply=incorporationRow[3], time=incorporationRow[6], blockchainReference=incorporationRow[7], activeAddress_no=numberOf_distinctAddresses, totalTransactions=numberOf_transactions, associatedSmartContracts=associatedContractList), 206
else:
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
except Exception as e:
print("tokenInfo:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
@app.route('/api/v2/tokenTransactions/<token>', methods=['GET'])
@ -1860,13 +1967,19 @@ async def tokenTransactions(token):
if os.path.isfile(filelocation):
transactionJsonData = fetch_token_transactions(token, senderFloAddress, destFloAddress, limit, use_AND)
sortedFormattedTransactions = sort_transactions(transactionJsonData)
return jsonify(token=token, transactions=sortedFormattedTransactions), 200
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, token=token, transactions=sortedFormattedTransactions), 206
else:
return jsonify(token=token, transactions=sortedFormattedTransactions), 200
else:
return jsonify(description='Token with the given name doesn\'t exist'), 404
if not is_backend_ready():
return jsonify(description=BACKEND_NOT_READY_ERROR), 503
else:
return jsonify(description='Token with the given name doesn\'t exist'), 404
except Exception as e:
print("tokenTransactions:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
@app.route('/api/v2/tokenBalances/<token>', methods=['GET'])
@ -1880,19 +1993,25 @@ async def tokenBalances(token):
conn = sqlite3.connect(dblocation)
c = conn.cursor()
else:
return jsonify(description="Token doesn't exist"), 404
if not is_backend_ready():
return jsonify(description=BACKEND_NOT_READY_ERROR), 503
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
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, token=token, balances=returnList), 206
else:
return jsonify(token=token, balances=returnList), 200
except Exception as e:
print("tokenBalances:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
# FLO Address APIs
@app.route('/api/v2/floAddressInfo/<floAddress>', methods=['GET'])
@ -1946,11 +2065,14 @@ async def floAddressInfo(floAddress):
tempdict['blockHash'] = contract[6]
incorporatedSmartContracts.append(tempdict)
return jsonify(floAddress=floAddress, floAddressBalances=detailList, incorporatedSmartContracts=incorporatedSmartContracts), 200
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, floAddress=floAddress, floAddressBalances=detailList, incorporatedSmartContracts=incorporatedSmartContracts), 206
else:
return jsonify(floAddress=floAddress, floAddressBalances=detailList, incorporatedSmartContracts=incorporatedSmartContracts), 200
except Exception as e:
print("floAddressInfo:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
@app.route('/api/v2/floAddressBalance/<floAddress>', methods=['GET'])
@ -1985,25 +2107,37 @@ async def floAddressBalance(floAddress):
tempdict['balance'] = balance
tempdict['token'] = token
detailList[token] = tempdict
return jsonify(floAddress=floAddress, floAddressBalances=detailList), 200
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, floAddress=floAddress, floAddressBalances=detailList), 206
else:
return jsonify(floAddress=floAddress, floAddressBalances=detailList), 200
else:
# Address is not associated with any token
return jsonify(floAddress=floAddress, floAddressBalances={}), 200
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, floAddress=floAddress, floAddressBalances={}), 206
else:
return jsonify(floAddress=floAddress, floAddressBalances={}), 200
else:
dblocation = DATA_PATH + '/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
if not is_backend_ready():
return jsonify(description=BACKEND_NOT_READY_ERROR), 503
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(floAddress=floAddress, token=token, balance=balance), 200
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, floAddress=floAddress, token=token, balance=balance), 206
else:
return jsonify(floAddress=floAddress, token=token, balance=balance), 200
except Exception as e:
print("floAddressBalance:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
@app.route('/api/v2/floAddressTransactions/<floAddress>', methods=['GET'])
@ -2030,7 +2164,10 @@ async def floAddressTransactions(floAddress):
if os.path.exists(dblocation):
tokenNames = [[str(token), ]]
else:
return jsonify(description="Token doesn't exist"), 404
if not is_backend_ready():
return jsonify(description=BACKEND_NOT_READY_ERROR), 503
else:
return jsonify(description="Token doesn't exist"), 404
if len(tokenNames) != 0:
allTransactionList = []
@ -2041,15 +2178,24 @@ async def floAddressTransactions(floAddress):
sortedFormattedTransactions = sort_transactions(allTransactionList)
if token is None:
return jsonify(floAddress=floAddress, transactions=sortedFormattedTransactions), 200
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, floAddress=floAddress, transactions=sortedFormattedTransactions), 206
else:
return jsonify(floAddress=floAddress, transactions=sortedFormattedTransactions), 200
else:
return jsonify(floAddress=floAddress, transactions=sortedFormattedTransactions, token=token), 200
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, floAddress=floAddress, transactions=sortedFormattedTransactions, token=token), 206
else:
return jsonify(floAddress=floAddress, transactions=sortedFormattedTransactions, token=token), 200
else:
return jsonify(floAddress=floAddress, transactions=[], token=token), 200
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, floAddress=floAddress, transactions=[], token=token), 206
else:
return jsonify(floAddress=floAddress, transactions=[], token=token), 200
except Exception as e:
print("floAddressTransactions:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
# SMART CONTRACT APIs
@ -2076,12 +2222,15 @@ async def getContractList_v2():
committeeAddressList = refresh_committee_list(APP_ADMIN, apiUrl, int(time.time()))
return jsonify(smartContracts=smart_contracts_morphed, smartContractCommittee=committeeAddressList), 200
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, smartContracts=smart_contracts_morphed, smartContractCommittee=committeeAddressList), 206
else:
return jsonify(smartContracts=smart_contracts_morphed, smartContractCommittee=committeeAddressList), 200
except Exception as e:
print("getContractList_v2:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
@app.route('/api/v2/smartContractInfo', methods=['GET'])
async def getContractInfo_v2():
@ -2157,13 +2306,19 @@ async def getContractInfo_v2():
returnval['closeDate'] = status_time_info[3]
returnval['contractSubtype'] = 'time-trigger'
return jsonify(contractName=contractName, contractAddress=contractAddress, contractInfo=returnval), 200
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, contractName=contractName, contractAddress=contractAddress, contractInfo=returnval), 206
else:
return jsonify(contractName=contractName, contractAddress=contractAddress, contractInfo=returnval), 200
else:
return jsonify(details="Smart Contract with the given name doesn't exist"), 404
if not is_backend_ready():
return jsonify(description=BACKEND_NOT_READY_ERROR), 503
else:
return jsonify(details="Smart Contract with the given name doesn't exist"), 404
except Exception as e:
print("getContractInfo_v2:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
@app.route('/api/v2/smartContractParticipants', methods=['GET'])
@ -2213,7 +2368,10 @@ 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
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, contractName=contractName, contractAddress=contractAddress, contractType=contractStructure['contractType'], contractSubtype='external-trigger', participantInfo=returnval), 206
else:
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')
@ -2223,7 +2381,10 @@ 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
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, contractName=contractName, contractAddress=contractAddress, contractType=contractStructure['contractType'], contractSubtype='time-trigger', participantInfo=returnval), 206
else:
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()
@ -2240,13 +2401,19 @@ async def getcontractparticipants_v2():
}
returnval.append(participation)
conn.close()
return jsonify(contractName=contractName, contractAddress=contractAddress, contractType=contractStructure['contractType'], contractSubtype=contractStructure['subtype'], participantInfo=returnval), 200
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, contractName=contractName, contractAddress=contractAddress, contractType=contractStructure['contractType'], contractSubtype=contractStructure['subtype'], participantInfo=returnval), 206
else:
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
if not is_backend_ready():
return jsonify(description=BACKEND_NOT_READY_ERROR), 503
else:
return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404
except Exception as e:
print("getcontractparticipants_v2:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
@app.route('/api/v2/participantDetails/<floAddress>', methods=['GET'])
@ -2399,15 +2566,21 @@ async def participantDetails(floAddress):
detailsDict['userChoice'] = result[0][0]
participationDetailsList.append(detailsDict)
return jsonify(floAddress=floAddress, type='participant', participatedContracts=participationDetailsList), 200
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, floAddress=floAddress, type='participant', participatedContracts=participationDetailsList), 206
else:
return jsonify(floAddress=floAddress, type='participant', participatedContracts=participationDetailsList), 200
else:
return jsonify(description="Address hasn't participated in any other contract"), 404
if not is_backend_ready():
return jsonify(description=BACKEND_NOT_READY_ERROR), 503
else:
return jsonify(description="Address hasn't participated in any other contract"), 404
else:
return jsonify(description='System error. System.db is missing. This is unusual, please report on https://github.com/ranchimall/ranchimallflo-api'), 500
except Exception as e:
print("participantDetails:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
@app.route('/api/v2/smartContractTransactions', methods=['GET'])
@ -2439,13 +2612,19 @@ async def smartcontracttransactions():
# Make db connection and fetch data
transactionJsonData = fetch_contract_transactions(contractName, contractAddress, _from, to)
transactionJsonData = sort_transactions(transactionJsonData)
return jsonify(contractName=contractName, contractAddress=contractAddress, contractTransactions=transactionJsonData), 200
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, contractName=contractName, contractAddress=contractAddress, contractTransactions=transactionJsonData), 206
else:
return jsonify(contractName=contractName, contractAddress=contractAddress, contractTransactions=transactionJsonData), 200
else:
return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404
if not is_backend_ready():
return jsonify(description=BACKEND_NOT_READY_ERROR), 503
else:
return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404
except Exception as e:
print("smartcontracttransactions:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
# todo - add options to only ask for active/consumed/returned deposits
@ -2492,14 +2671,20 @@ async def smartcontractdeposits():
deposit_info.append(obj)
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
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, currentDepositBalance=currentDepositBalance, depositInfo=deposit_info), 206
else:
return jsonify(currentDepositBalance=currentDepositBalance, depositInfo=deposit_info), 200
else:
return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404
if not is_backend_ready():
return jsonify(description=BACKEND_NOT_READY_ERROR), 503
else:
return jsonify(description='Smart Contract with the given name doesn\'t exist'), 404
except Exception as e:
print("smartcontractdeposits:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
@app.route('/api/v2/blockDetails/<blockHash>', methods=['GET'])
async def blockdetails(blockHash):
@ -2510,10 +2695,13 @@ async def blockdetails(blockHash):
blockJson = json.loads(blockJson[0][0])
return jsonify(blockDetails=blockJson), 200
else:
return jsonify(description='Block doesn\'t exist in database'), 404
if not is_backend_ready():
return jsonify(description=BACKEND_NOT_READY_ERROR), 503
else:
return jsonify(description='Block doesn\'t exist in database'), 404
except Exception as e:
print("blockdetails:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
@ -2612,11 +2800,14 @@ async def transactiondetails1(transactionHash):
mergeTx['operationDetails'] = operationDetails
return jsonify(mergeTx), 200
else:
return jsonify(description='Transaction doesn\'t exist in database'), 404
if not is_backend_ready():
return jsonify(description=BACKEND_NOT_READY_ERROR), 503
else:
return jsonify(description='Transaction doesn\'t exist in database'), 404
except Exception as e:
print("transactiondetails1:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
@app.route('/api/v2/latestTransactionDetails', methods=['GET'])
@ -2652,10 +2843,13 @@ async def latestTransactionDetails():
# 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
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, latestTransactions=tx_list), 206
else:
return jsonify(latestTransactions=tx_list), 200
except Exception as e:
print("latestTransactionDetails:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
@ -2684,11 +2878,14 @@ async def latestBlockDetails():
for idx, item in enumerate(latestBlocks):
templst.append(json.loads(item[0]))
return jsonify(latestBlocks=templst), 200
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, latestBlocks=templst), 206
else:
return jsonify(latestBlocks=templst), 200
except Exception as e:
print("latestBlockDetails:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
@app.route('/api/v2/blockTransactions/<blockHash>', methods=['GET'])
@ -2709,12 +2906,15 @@ async def blocktransactions(blockHash):
#blocktxs['onChain'] = True
return jsonify(transactions=blocktxs, blockKeyword=blockHash), 200
else:
return jsonify(description='Block doesn\'t exist in database'), 404
if not is_backend_ready():
return jsonify(description=BACKEND_NOT_READY_ERROR), 503
else:
return jsonify(description='Block doesn\'t exist in database'), 404
except Exception as e:
print("blocktransactions:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
@app.route('/api/v2/categoriseString/<urlstring>')
async def categoriseString_v2(urlstring):
@ -2748,7 +2948,7 @@ async def categoriseString_v2(urlstring):
except Exception as e:
print("categoriseString_v2:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
@app.route('/api/v2/tokenSmartContractList', methods=['GET'])
@ -2778,12 +2978,15 @@ async def tokenSmartContractList():
conn.close()
committeeAddressList = refresh_committee_list(APP_ADMIN, apiUrl, int(time.time()))
return jsonify(tokens=filelist, smartContracts=smart_contracts_morphed, smartContractCommittee=committeeAddressList), 200
if not is_backend_ready():
return jsonify(warning=BACKEND_NOT_READY_WARNING, tokens=filelist, smartContracts=smart_contracts_morphed, smartContractCommittee=committeeAddressList), 206
else:
return jsonify(tokens=filelist, smartContracts=smart_contracts_morphed, smartContractCommittee=committeeAddressList), 200
except Exception as e:
print("tokenSmartContractList:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500
class ServerSentEvent:
def __init__(
@ -2850,7 +3053,7 @@ async def priceData():
return jsonify(prices=prices), 200
except Exception as e:
print("priceData:", e)
return jsonify(result='error', description=INTERNAL_ERROR)
return jsonify(description=INTERNAL_ERROR), 500

View File

@ -24,6 +24,7 @@ import websockets
from decimal import Decimal
import pdb
from src.backend.util_rollback import rollback_to_block
from src.flags import set_backend_start, set_backend_stop, set_backend_sync_start, set_backend_sync_stop, set_backend_ready, set_backend_not_ready, is_backend_ready, is_backend_syncing
RETRY_TIMEOUT_LONG = 30 * 60 # 30 mins
@ -1110,6 +1111,7 @@ def check_for_reorg(backtrack_count = BACK_TRACK_BLOCKS):
# rollback if needed
if block_number != latest_block:
set_backend_not_ready()
stop_sync_loop() # stop the syncing process
rollback_to_block(block_number)
@ -2527,6 +2529,7 @@ def scanBlockchain(startup = False):
# processBlock(blockindex=blockindex)
# At this point the script has updated to the latest block
set_backend_ready()
# Now we connect to Blockbook's websocket API to get information about the latest blocks
if not startup and not isactive_sync_loop():
start_sync_loop()
@ -2567,11 +2570,10 @@ def get_websocket_uri(testnet=False):
async def connect_to_websocket(uri):
# global flag to pass termination when needed
global _isactive_sync
_isactive_sync = True
set_backend_sync_start()
while True:
if not _isactive_sync:
if not is_backend_syncing():
return
try:
async with websockets.connect(uri) as websocket:
@ -2582,7 +2584,7 @@ async def connect_to_websocket(uri):
}
await websocket.send(json.dumps(subscription_request))
while True:
if not _isactive_sync:
if not is_backend_syncing():
websocket.close()
return scanBlockchain()
response = await websocket.recv()
@ -2598,7 +2600,7 @@ async def connect_to_websocket(uri):
# If this is the issue need to proceed forward only once blockbook has consolitated
check_for_reorg()
if not _isactive_sync: #if reorg happens, _isactive_sync becomes False as sync is closed
if not is_backend_syncing(): #if reorg happens, is_backend_syncing() becomes False as sync is closed
websocket.close()
return scanBlockchain()
processBlock(blockindex=response['data']['height'], blockhash=response['data']['hash'])
@ -2607,7 +2609,7 @@ async def connect_to_websocket(uri):
logger.info(f"Connection error: {e}")
# Add a delay before attempting to reconnect
await asyncio.sleep(5) # You can adjust the delay as needed
if not _isactive_sync:
if not is_backend_syncing():
return
scanBlockchain()
@ -2738,8 +2740,8 @@ def isactive_sync_loop():
return False
def stop_sync_loop():
global _sync_loop, _isactive_sync
_isactive_sync = False
set_backend_sync_stop()
global _sync_loop
if (_sync_loop is not None) and _sync_loop.is_running():
_sync_loop.stop()
_sync_loop = None
@ -2747,6 +2749,8 @@ def stop_sync_loop():
def start_backend_process(config, reset = False):
global _config
_config = config
set_backend_start()
set_backend_not_ready()
initiate_process()
init_storage_if_not_exist(reset)
# MAIN LOGIC STARTS

52
src/flags.py Normal file
View File

@ -0,0 +1,52 @@
FLAGS = {}
FLAGS["is_running"] = None
FLAGS["is_backend_active"] = None
FLAGS["is_backend_syncing"] = None
FLAGS["is_backend_ready"] = None
FLAGS["is_api_server_active"] = None
def is_running():
return bool(FLAGS["is_running"])
def set_run_start():
FLAGS["is_running"] = True
def set_run_stop():
FLAGS["is_running"] = False
def set_backend_start():
FLAGS["is_backend_active"] = True
def set_backend_stop():
FLAGS["is_backend_active"] = False
def is_backend_active():
return bool(FLAGS["is_backend_active"])
def set_backend_sync_start():
FLAGS["is_backend_syncing"] = True
def set_backend_sync_stop():
FLAGS["is_backend_syncing"] = False
def is_backend_syncing():
return bool(FLAGS["is_backend_syncing"])
def set_backend_ready():
FLAGS["is_backend_ready"] = True
def set_backend_not_ready():
FLAGS["is_backend_ready"] = False
def is_backend_ready():
return bool(FLAGS["is_backend_ready"])
def set_api_start():
FLAGS["is_api_server_active"] = True
def set_api_stop():
FLAGS["is_api_server_active"] = False
def is_api_active():
return bool(FLAGS["is_api_server_active"])