1154 lines
68 KiB
Python
Executable File
1154 lines
68 KiB
Python
Executable File
import requests
|
|
import json
|
|
import sqlite3
|
|
import argparse
|
|
import configparser
|
|
import subprocess
|
|
import sys
|
|
import parsing
|
|
import time
|
|
import os
|
|
import shutil
|
|
from sqlalchemy.orm import sessionmaker, relationship
|
|
from sqlalchemy import create_engine, func, desc
|
|
from models import SystemData, ActiveTable, ConsumedTable, TransferLogs, TransactionHistory, Base, ContractStructure, ContractBase, ContractParticipants, SystemBase, ActiveContracts, ContractParticipantMapping, LatestTransactions, LatestCacheBase, LatestBlocks
|
|
from config import *
|
|
import pybtc
|
|
import socketio
|
|
|
|
|
|
def pushData_SSEapi(message):
|
|
signature = pybtc.sign_message(message.encode(), privKey)
|
|
headers = {'Accept': 'application/json', 'Content-Type': 'application/json', 'Signature': signature}
|
|
r = requests.post(sseAPI_url, json={'message': '{}'.format(message)}, headers=headers)
|
|
|
|
|
|
def processBlock(blockindex):
|
|
print(blockindex)
|
|
# Scan every block
|
|
string = "{} getblockhash {}".format(localapi, str(blockindex))
|
|
response = subprocess.check_output(string, shell=True)
|
|
blockhash = response.decode("utf-8")
|
|
|
|
string = "{} getblock {}".format(localapi, str(blockhash))
|
|
response = subprocess.check_output(string, shell=True)
|
|
blockinfo = json.loads(response.decode("utf-8"))
|
|
|
|
# todo Rule 8 - read every transaction from every block to find and parse flodata
|
|
|
|
# Scan every transaction
|
|
for transaction in blockinfo["tx"]:
|
|
string = "{} getrawtransaction {} 1".format(localapi, str(transaction))
|
|
response = subprocess.check_output(string, shell=True)
|
|
transaction_data = json.loads(response.decode("utf-8"))
|
|
text = transaction_data["floData"]
|
|
text = text.replace("\n", " \n ")
|
|
|
|
# todo Rule 9 - Reject all noise transactions. Further rules are in parsing.py
|
|
|
|
parsed_data = parsing.parse_flodata(text, blockinfo)
|
|
if parsed_data['type'] != 'noise':
|
|
print(blockindex)
|
|
print(parsed_data['type'])
|
|
returnval = startWorking(transaction_data, parsed_data, blockinfo)
|
|
|
|
if returnval != 0:
|
|
updateLatestBlock(blockinfo)
|
|
|
|
engine = create_engine('sqlite:///system.db')
|
|
SystemBase.metadata.create_all(bind=engine)
|
|
session = sessionmaker(bind=engine)()
|
|
entry = session.query(SystemData).filter(SystemData.attribute == 'lastblockscanned').all()[0]
|
|
entry.value = str(blockindex)
|
|
session.commit()
|
|
session.close()
|
|
|
|
# Check smartContracts which will be triggered locally, and not by the contract committee
|
|
checkLocaltriggerContracts(blockinfo)
|
|
|
|
|
|
def processApiBlock(blockhash):
|
|
|
|
string = "{} getblock {}".format(localapi, str(blockhash))
|
|
response = subprocess.check_output(string, shell=True)
|
|
blockinfo = json.loads(response.decode("utf-8"))
|
|
|
|
# todo Rule 8 - read every transaction from every block to find and parse flodata
|
|
|
|
# Scan every transaction
|
|
for transaction in blockinfo["tx"]:
|
|
string = "{} getrawtransaction {} 1".format(localapi, str(transaction))
|
|
response = subprocess.check_output(string, shell=True)
|
|
transaction_data = json.loads(response.decode("utf-8"))
|
|
text = transaction_data["floData"]
|
|
text = text.replace("\n", " \n ")
|
|
|
|
# todo Rule 9 - Reject all noise transactions. Further rules are in parsing.py
|
|
|
|
parsed_data = parsing.parse_flodata(text, blockinfo)
|
|
if parsed_data['type'] != 'noise':
|
|
print(blockindex)
|
|
print(parsed_data['type'])
|
|
startWorking(transaction_data, parsed_data, blockinfo)
|
|
|
|
engine = create_engine('sqlite:///system.db')
|
|
SystemBase.metadata.create_all(bind=engine)
|
|
session = sessionmaker(bind=engine)()
|
|
entry = session.query(SystemData).filter(SystemData.attribute == 'lastblockscanned').all()[0]
|
|
entry.value = str(blockindex)
|
|
session.commit()
|
|
session.close()
|
|
|
|
# Check smartContracts which will be triggered locally, and not by the contract committee
|
|
checkLocaltriggerContracts(blockinfo)
|
|
|
|
|
|
def updateLatestTransaction(transactionData, parsed_data, blockinfo):
|
|
# connect to latest transaction db
|
|
conn = sqlite3.connect('latestCache.db')
|
|
conn.execute("INSERT INTO latestTransactions(transactionHash, blockNumber, jsonData, transactionType, parsedFloData) VALUES (?,?,?,?,?)", (transactionData['hash'], blockinfo['height'], json.dumps(transactionData), parsed_data['type'], json.dumps(parsed_data)))
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
def updateLatestBlock(blockData):
|
|
# connect to latest block db
|
|
conn = sqlite3.connect('latestCache.db')
|
|
conn.execute('INSERT INTO latestBlocks(blockNumber, blockHash, jsonData) VALUES (?,?,?)',(blockData['height'], blockData['hash'], json.dumps(blockData)))
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
|
|
def transferToken(tokenIdentification, tokenAmount, inputAddress, outputAddress, transaction_data=None):
|
|
engine = create_engine('sqlite:///tokens/{}.db'.format(tokenIdentification), echo=True)
|
|
Base.metadata.create_all(bind=engine)
|
|
session = sessionmaker(bind=engine)()
|
|
availableTokens = session.query(func.sum(ActiveTable.transferBalance)).filter_by(address=inputAddress).all()[0][0]
|
|
commentTransferAmount = float(tokenAmount)
|
|
if availableTokens is None:
|
|
print("The input address dosen't exist in our database ")
|
|
session.close()
|
|
return 0
|
|
|
|
elif availableTokens < commentTransferAmount:
|
|
print("\nThe transfer amount passed in the comments is more than the user owns\nThis transaction will be discarded\n")
|
|
session.close()
|
|
return 0
|
|
|
|
elif availableTokens >= commentTransferAmount:
|
|
table = session.query(ActiveTable).filter(ActiveTable.address==inputAddress).all()
|
|
string = "{} getblock {}".format(localapi, transaction_data['blockhash'])
|
|
response = subprocess.check_output(string, shell=True)
|
|
block_data = json.loads(response.decode("utf-8"))
|
|
|
|
pidlst = []
|
|
checksum = 0
|
|
for row in table:
|
|
if checksum >= commentTransferAmount:
|
|
break
|
|
pidlst.append([row.id, row.transferBalance])
|
|
checksum = checksum + row.transferBalance
|
|
|
|
if checksum == commentTransferAmount:
|
|
consumedpid_string = ''
|
|
|
|
# Update all pids in pidlist's transferBalance to 0
|
|
lastid = session.query(ActiveTable)[-1].id
|
|
for piditem in pidlst:
|
|
entry = session.query(ActiveTable).filter(ActiveTable.id == piditem[0]).all()
|
|
consumedpid_string = consumedpid_string + '{},'.format(piditem[0])
|
|
session.add(TransferLogs(sourceFloAddress=inputAddress, destFloAddress=outputAddress,
|
|
transferAmount=entry[0].transferBalance, sourceId=piditem[0], destinationId=lastid+1,
|
|
blockNumber=block_data['height'], time=block_data['time'],
|
|
transactionHash=transaction_data['txid']))
|
|
entry[0].transferBalance = 0
|
|
|
|
if len(consumedpid_string)>1:
|
|
consumedpid_string = consumedpid_string[:-1]
|
|
|
|
# Make new entry
|
|
session.add(ActiveTable(address=outputAddress, consumedpid=consumedpid_string,
|
|
transferBalance=commentTransferAmount))
|
|
|
|
# Migration
|
|
# shift pid of used utxos from active to consumed
|
|
for piditem in pidlst:
|
|
# move the parentids consumed to consumedpid column in both activeTable and consumedTable
|
|
entries = session.query(ActiveTable).filter(ActiveTable.parentid == piditem[0]).all()
|
|
for entry in entries:
|
|
entry.consumedpid = entry.consumedpid + ',{}'.format(piditem[0])
|
|
entry.parentid = None
|
|
|
|
entries = session.query(ConsumedTable).filter(ConsumedTable.parentid == piditem[0]).all()
|
|
for entry in entries:
|
|
entry.consumedpid = entry.consumedpid + ',{}'.format(piditem[0])
|
|
entry.parentid = None
|
|
|
|
# move the pids consumed in the transaction to consumedTable and delete them from activeTable
|
|
session.execute(
|
|
'INSERT INTO consumedTable (id, address, parentid, consumedpid, transferBalance) SELECT id, address, parentid, consumedpid, transferBalance FROM activeTable WHERE id={}'.format(
|
|
piditem[0]))
|
|
session.execute('DELETE FROM activeTable WHERE id={}'.format(piditem[0]))
|
|
session.commit()
|
|
session.commit()
|
|
|
|
if checksum > commentTransferAmount:
|
|
consumedpid_string = ''
|
|
# Update all pids in pidlist's transferBalance
|
|
lastid = session.query(ActiveTable)[-1].id
|
|
for idx, piditem in enumerate(pidlst):
|
|
entry = session.query(ActiveTable).filter(ActiveTable.id == piditem[0]).all()
|
|
if idx != len(pidlst) - 1:
|
|
session.add(TransferLogs(sourceFloAddress=inputAddress, destFloAddress=outputAddress,
|
|
transferAmount=entry[0].transferBalance, sourceId=piditem[0],
|
|
destinationId=lastid + 1,
|
|
blockNumber=block_data['height'], time=block_data['time'],
|
|
transactionHash=transaction_data['txid']))
|
|
entry[0].transferBalance = 0
|
|
consumedpid_string = consumedpid_string + '{},'.format(piditem[0])
|
|
else:
|
|
session.add(TransferLogs(sourceFloAddress=inputAddress, destFloAddress=outputAddress,
|
|
transferAmount=piditem[1]-(checksum - commentTransferAmount), sourceId=piditem[0],
|
|
destinationId=lastid + 1,
|
|
blockNumber=block_data['height'], time=block_data['time'],
|
|
transactionHash=transaction_data['txid']))
|
|
entry[0].transferBalance = checksum - commentTransferAmount
|
|
|
|
|
|
if len(consumedpid_string) > 1:
|
|
consumedpid_string = consumedpid_string[:-1]
|
|
|
|
# Make new entry
|
|
session.add(ActiveTable(address=outputAddress, parentid= pidlst[-1][0], consumedpid=consumedpid_string,
|
|
transferBalance=commentTransferAmount))
|
|
|
|
# Migration
|
|
# shift pid of used utxos from active to consumed
|
|
for piditem in pidlst[:-1]:
|
|
# move the parentids consumed to consumedpid column in both activeTable and consumedTable
|
|
entries = session.query(ActiveTable).filter(ActiveTable.parentid == piditem[0]).all()
|
|
for entry in entries:
|
|
entry.consumedpid = entry.consumedpid + ',{}'.format(piditem[0])
|
|
entry.parentid = None
|
|
|
|
entries = session.query(ConsumedTable).filter(ConsumedTable.parentid == piditem[0]).all()
|
|
for entry in entries:
|
|
entry.consumedpid = entry.consumedpid + ',{}'.format(piditem[0])
|
|
entry.parentid = None
|
|
|
|
# move the pids consumed in the transaction to consumedTable and delete them from activeTable
|
|
session.execute(
|
|
'INSERT INTO consumedTable (id, address, parentid, consumedpid, transferBalance) SELECT id, address, parentid, consumedpid, transferBalance FROM activeTable WHERE id={}'.format(
|
|
piditem[0]))
|
|
session.execute('DELETE FROM activeTable WHERE id={}'.format(piditem[0]))
|
|
session.commit()
|
|
session.commit()
|
|
|
|
string = "{} getblock {}".format(localapi, transaction_data['blockhash'])
|
|
response = subprocess.check_output(string, shell=True)
|
|
block_data = json.loads(response.decode("utf-8"))
|
|
blockchainReference = neturl + 'tx/' + transaction_data['txid']
|
|
session.add(TransactionHistory(sourceFloAddress=inputAddress, destFloAddress=outputAddress,
|
|
transferAmount=tokenAmount, blockNumber=block_data['height'], time=block_data['time'],
|
|
transactionHash=transaction_data['txid'], blockchainReference=blockchainReference))
|
|
session.commit()
|
|
session.close()
|
|
return 1
|
|
|
|
|
|
def checkLocaltriggerContracts(blockinfo):
|
|
engine = create_engine('sqlite:///system.db', echo=False)
|
|
connection = engine.connect()
|
|
# todo : filter activeContracts which only have local triggers
|
|
activeContracts = connection.execute('select contractName, contractAddress from activecontracts where status=="active" ').fetchall()
|
|
connection.close()
|
|
|
|
for contract in activeContracts:
|
|
# Check if the contract has blockchain trigger or committee trigger
|
|
engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(contract[0],contract[1]), echo=False)
|
|
connection = engine.connect()
|
|
# todo : filter activeContracts which only have local triggers
|
|
contractStructure = connection.execute('select * from contractstructure').fetchall()
|
|
contractStructure_T = list(zip(*contractStructure))
|
|
|
|
if 'exitconditions' in list(contractStructure_T[1]):
|
|
# This is a committee trigger contract
|
|
expiryTime = connection.execute('select value from contractstructure where attribute=="expiryTime"').fetchall()[0][0]
|
|
expirytime_split = expiryTime.split(' ')
|
|
parse_string = '{}/{}/{} {}'.format(expirytime_split[3], parsing.months[expirytime_split[1]],
|
|
expirytime_split[2], expirytime_split[4])
|
|
expirytime_object = parsing.arrow.get(parse_string, 'YYYY/M/D HH:mm:ss').replace(tzinfo=expirytime_split[5][3:])
|
|
blocktime_object = parsing.arrow.get(blockinfo['time']).to('Asia/Kolkata')
|
|
|
|
if blocktime_object > expirytime_object:
|
|
if 'minimumsubscriptionamount' in list(contractStructure_T[1]):
|
|
minimumsubscriptionamount = connection.execute('select value from contractstructure where attribute=="minimumsubscriptionamount"').fetchall()[0][0]
|
|
tokenAmount_sum = connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0]
|
|
if tokenAmount_sum < minimumsubscriptionamount:
|
|
# Initialize payback to contract participants
|
|
contractParticipants = connection.execute('select participantAddress, tokenAmount, transactionHash from contractparticipants').fetchall()[0][0]
|
|
|
|
for participant in contractParticipants:
|
|
tokenIdentification = connection.execute('select * from contractstructure where attribute="tokenIdentification"').fetchall()[0][0]
|
|
contractAddress = connection.execute('select * from contractstructure where attribute="contractAddress"').fetchall()[0][0]
|
|
returnval = transferToken(tokenIdentification, participant[1], contractAddress, participant[0])
|
|
if returnval is None:
|
|
print("Something went wrong in the token transfer method while doing local Smart Contract Trigger. THIS IS CRITICAL ERROR")
|
|
return
|
|
connection.execute(
|
|
'update contractparticipants set winningAmount="{}" where participantAddress="{}" and transactionHash="{}"'.format(
|
|
(participant[1], participant[0], participant[2])))
|
|
engine = create_engine('sqlite:///system.db', echo=True)
|
|
connection = engine.connect()
|
|
connection.execute(
|
|
'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format(contract[0], contract[1]))
|
|
connection.execute(
|
|
'update activecontracts set closeDate="{}" where contractName="{}" and contractAddress="{}"'.format(blockinfo['time'],
|
|
contract[0], contract[1]))
|
|
connection.close()
|
|
|
|
engine = create_engine('sqlite:///system.db', echo=True)
|
|
connection = engine.connect()
|
|
connection.execute(
|
|
'update activecontracts set status="expired" where contractName="{}" and contractAddress="{}"'.format(
|
|
contract[0], contract[1]))
|
|
connection.execute(
|
|
'update activecontracts set expirydate="{}" where contractName="{}" and contractAddress="{}"'.format(blockinfo['time'],
|
|
contract[0], contract[1]))
|
|
connection.close()
|
|
|
|
|
|
else:
|
|
# This is a blockchain trigger contract
|
|
if 'maximumsubscriptionamount' in list(contractStructure_T[1]):
|
|
maximumsubscriptionamount = connection.execute('select value from contractstructure where attribute=="maximumsubscriptionamount"').fetchall()[0][0]
|
|
tokenAmount_sum = connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0]
|
|
if tokenAmount_sum >= maximumsubscriptionamount:
|
|
# Trigger the contract
|
|
payeeAddress = connection.execute('select * from contractstructure where attribute="payeeAddress"').fetchall()[0][0]
|
|
tokenIdentification = connection.execute('select * from contractstructure where attribute="tokenIdentification"').fetchall()[0][0]
|
|
contractAddress = connection.execute('select * from contractstructure where attribute="contractAddress"').fetchall()[0][0]
|
|
returnval = transferToken(tokenIdentification, tokenAmount_sum, contractAddress, payeeAddress)
|
|
if returnval is None:
|
|
print("Something went wrong in the token transfer method while doing local Smart Contract Trigger")
|
|
return
|
|
connection.execute(
|
|
'update contractparticipants set winningAmount="{}"'.format(
|
|
(0)))
|
|
engine = create_engine('sqlite:///system.db', echo=False)
|
|
connection = engine.connect()
|
|
connection.execute(
|
|
'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format(contract[0], contract[1]))
|
|
connection.execute(
|
|
'update activecontracts set closeDate="{}" where contractName="{}" and contractAddress="{}"'.format(
|
|
blockinfo['time'], contract[0], contract[1]))
|
|
connection.close()
|
|
|
|
expiryTime = connection.execute('select value from contractstructure where attribute=="expiryTime"').fetchall()[0][0]
|
|
expirytime_split = expiryTime.split(' ')
|
|
parse_string = '{}/{}/{} {}'.format(expirytime_split[3], parsing.months[expirytime_split[1]], expirytime_split[2], expirytime_split[4])
|
|
expirytime_object = parsing.arrow.get(parse_string, 'YYYY/M/D HH:mm:ss').replace(
|
|
tzinfo=expirytime_split[5][3:])
|
|
blocktime_object = parsing.arrow.get(blockinfo['time']).to('Asia/Kolkata')
|
|
|
|
if blocktime_object > expirytime_object:
|
|
if 'minimumsubscriptionamount' in list(contractStructure_T[1]):
|
|
minimumsubscriptionamount = connection.execute('select value from contractstructure where attribute=="minimumsubscriptionamount"').fetchall()[0][0]
|
|
tokenAmount_sum = connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0]
|
|
if tokenAmount_sum < minimumsubscriptionamount:
|
|
# Initialize payback to contract participants
|
|
contractParticipants = connection.execute(
|
|
'select participantAddress, tokenAmount, transactionHash from contractparticipants').fetchall()[0][0]
|
|
|
|
for participant in contractParticipants:
|
|
tokenIdentification = connection.execute(
|
|
'select * from contractstructure where attribute="tokenIdentification"').fetchall()[0][
|
|
0]
|
|
contractAddress = connection.execute(
|
|
'select * from contractstructure where attribute="contractAddress"').fetchall()[0][0]
|
|
returnval = transferToken(tokenIdentification, participant[1], contractAddress,
|
|
participant[0])
|
|
if returnval is None:
|
|
print(
|
|
"Something went wrong in the token transfer method while doing local Smart Contract Trigger")
|
|
return
|
|
connection.execute('update contractparticipants set winningAmount="{}" where participantAddress="{}" and transactionHash="{}"'.format((participant[1], participant[0], participant[2])))
|
|
engine = create_engine('sqlite:///system.db', echo=False)
|
|
connection = engine.connect()
|
|
connection.execute(
|
|
'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format(
|
|
contract[0], contract[1]))
|
|
connection.execute(
|
|
'update activecontracts set closeDate="{}" where contractName="{}" and contractAddress="{}"'.format(
|
|
blockinfo['time'],contract[0], contract[1]))
|
|
connection.close()
|
|
|
|
# Trigger the contract
|
|
payeeAddress = connection.execute('select * from contractstructure where attribute="payeeAddress"').fetchall()[0][0]
|
|
tokenIdentification = connection.execute('select * from contractstructure where attribute="tokenIdentification"').fetchall()[0][0]
|
|
contractAddress = connection.execute('select * from contractstructure where attribute="contractAddress"').fetchall()[0][0]
|
|
returnval = transferToken(tokenIdentification, tokenAmount_sum, contractAddress, payeeAddress, transaction_data)
|
|
if returnval is None:
|
|
print("Something went wrong in the token transfer method while doing local Smart Contract Trigger")
|
|
return
|
|
connection.execute('update contractparticipants set winningAmount="{}"'.format(0))
|
|
engine = create_engine('sqlite:///system.db', echo=False)
|
|
connection = engine.connect()
|
|
connection.execute(
|
|
'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format(
|
|
contract[0], contract[1]))
|
|
connection.execute(
|
|
'update activecontracts set closeDate="{}" where contractName="{}" and contractAddress="{}"'.format(
|
|
blockinfo['time'], contract[0], contract[1]))
|
|
connection.close()
|
|
|
|
|
|
|
|
def startWorking(transaction_data, parsed_data, blockinfo):
|
|
|
|
# Do the necessary checks for the inputs and outputs
|
|
|
|
# todo Rule 38 - Here we are doing FLO processing. We attach asset amounts to a FLO address, so every FLO address
|
|
# will have multiple feed ins of the asset. Each of those feedins will be an input to the address.
|
|
# an address can also spend the asset. Each of those spends is an output of that address feeding the asset into some
|
|
# other address an as input
|
|
|
|
# Rule 38 reframe - For checking any asset transfer on the flo blockchain it is possible that some transactions may use more than one
|
|
# vins. However in any single transaction the system considers valid, they can be only one source address from which the flodata is
|
|
# originting. To ensure consistency, we will have to check that even if there are more than one vins in a transaction, there should be
|
|
# excatly one FLO address on the originating side and that FLO address should be the owner of the asset tokens being transferred
|
|
|
|
# Create vinlist and outputlist
|
|
vinlist = []
|
|
querylist = []
|
|
|
|
# todo Rule 39 - Create a list of vins for a given transaction id
|
|
for obj in transaction_data["vin"]:
|
|
querylist.append([obj["txid"], obj["vout"]])
|
|
|
|
|
|
totalinputval = 0
|
|
inputadd = ''
|
|
|
|
# todo Rule 40 - For each vin, find the feeding address and the fed value. Make an inputlist containing [inputaddress, n value]
|
|
for query in querylist:
|
|
string = "{} getrawtransaction {} 1".format(localapi, str(query[0]))
|
|
response = subprocess.check_output(string, shell=True)
|
|
content = json.loads(response.decode("utf-8"))
|
|
|
|
for objec in content["vout"]:
|
|
if objec["n"] == query[1]:
|
|
inputadd = objec["scriptPubKey"]["addresses"][0]
|
|
totalinputval = totalinputval + objec["value"]
|
|
vinlist.append([inputadd, objec["value"]])
|
|
|
|
# todo Rule 41 - Check if all the addresses in a transaction on the input side are the same
|
|
for idx, item in enumerate(vinlist):
|
|
if idx == 0:
|
|
temp = item[0]
|
|
continue
|
|
if item[0] != temp:
|
|
print('System has found more than one address as part of vin')
|
|
print('This transaction will be rejected')
|
|
return 0
|
|
|
|
inputlist = [vinlist[0][0], totalinputval]
|
|
|
|
|
|
# todo Rule 42 - If the number of vout is more than 2, reject the transaction
|
|
if len(transaction_data["vout"]) > 2:
|
|
print("Program has detected more than 2 vouts ")
|
|
print("This transaction will be discarded")
|
|
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 = []
|
|
for obj in transaction_data["vout"]:
|
|
if obj["scriptPubKey"]["type"] == "pubkeyhash":
|
|
if inputlist[0] == obj["scriptPubKey"]["addresses"][0]:
|
|
continue
|
|
outputlist.append([obj["scriptPubKey"]["addresses"][0], obj["value"]])
|
|
|
|
if len(outputlist) != 1:
|
|
print("The transaction change is not coming back to the input address")
|
|
return 0
|
|
|
|
outputlist = outputlist[0]
|
|
|
|
print("\n\nInput Address")
|
|
print(inputlist)
|
|
print("\nOutput Address")
|
|
print(outputlist)
|
|
|
|
# All FLO checks completed at this point.
|
|
# Semantic rules for parsed data begins
|
|
|
|
# todo Rule 44 - Process as per the type of transaction
|
|
if parsed_data['type'] == 'transfer':
|
|
print('Found a transaction of the type transfer')
|
|
|
|
# todo Rule 45 - If the transfer type is token, then call the function transferToken to adjust the balances
|
|
if parsed_data['transferType'] == 'token':
|
|
# Check if the transaction hash already exists in the token db
|
|
engine = create_engine('sqlite:///tokens/{}.db'.format(parsed_data['tokenIdentification']), echo=True)
|
|
connection = engine.connect()
|
|
|
|
blockno_txhash = connection.execute('select blockNumber, transactionHash from transactionHistory').fetchall()
|
|
connection.close()
|
|
blockno_txhash_T = list(zip(*blockno_txhash))
|
|
|
|
if transaction_data['txid'] in list(blockno_txhash_T[1]):
|
|
print('Transaction {} already exists in the token db. This is unusual, please check your code'.format(transaction_data['txid']))
|
|
pushData_SSEapi('Error | Transaction {} already exists in the token db. This is unusual, please check your code'.format(transaction_data['txid']))
|
|
return 0
|
|
|
|
returnval = transferToken(parsed_data['tokenIdentification'], parsed_data['tokenAmount'], inputlist[0], outputlist[0], transaction_data)
|
|
if returnval is None:
|
|
print("Something went wrong in the token transfer method")
|
|
pushData_SSEapi('Error | Something went wrong while doing the internal db transactions for {}'.format(transaction_data['txid']))
|
|
return 0
|
|
else:
|
|
updateLatestTransaction(transaction_data, parsed_data, blockinfo)
|
|
|
|
# If this is the first interaction of the outputlist's address with the given token name, add it to token mapping
|
|
engine = create_engine('sqlite:///system.db', echo=True)
|
|
connection = engine.connect()
|
|
firstInteractionCheck = connection.execute('select * from tokenAddressMapping where tokenAddress="{}" and token="{}"'.format(outputlist[0], parsed_data['tokenIdentification'])).fetchall()
|
|
|
|
if len(firstInteractionCheck) == 0:
|
|
connection.execute('INSERT INTO tokenAddressMapping (tokenAddress, token, transactionHash) VALUES ("{}", "{}", "{}")'.format(outputlist[0], parsed_data['tokenIdentification'], transaction_data['txid']))
|
|
|
|
connection.close()
|
|
|
|
|
|
# Pass information to SSE channel
|
|
url = 'https://ranchimallflo.duckdns.org/'
|
|
headers = {'Accept': 'application/json', 'Content-Type': 'application/json'}
|
|
r = requests.post(url, json={'message': 'Token Transfer | name:{} | transactionHash:{}'.format(parsed_data['tokenIdentification'], transaction_data['txid'])}, headers=headers)
|
|
|
|
|
|
# todo Rule 46 - If the transfer type is smart contract, then call the function transferToken to do sanity checks & lock the balance
|
|
elif parsed_data['transferType'] == 'smartContract':
|
|
|
|
#if contractAddress was passed check if it matches the output address of this contract
|
|
if 'contractAddress' in parsed_data:
|
|
if parsed_data['contractAddress'] != outputlist[0]:
|
|
print('Mismatch in contract address specified in flodata and the output address of the transaction')
|
|
print('This contract transfer will be rejected')
|
|
|
|
# Pass information to SSE channel
|
|
pushData_SSEapi('Error| Mismatch in contract address specified in flodata and the output address of the transaction {}'.format(transaction_data['txid']))
|
|
return 0
|
|
|
|
# check if the contract is active
|
|
engine = create_engine('sqlite:///system.db', echo=True)
|
|
connection = engine.connect()
|
|
contractDetails = connection.execute(
|
|
'select contractName, contractAddress from activecontracts where status=="active"').fetchall()
|
|
connection.close()
|
|
contractList = []
|
|
|
|
counter = 0
|
|
for contract in contractDetails:
|
|
if contract[0] == parsed_data['contractName'] and contract[1] == outputlist[0]:
|
|
counter = counter + 1
|
|
|
|
if counter != 1:
|
|
print('Active Smart contract with the given name doesn\'t exist')
|
|
return 0
|
|
|
|
# Check if the contract has expired
|
|
engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True)
|
|
ContractBase.metadata.create_all(bind=engine)
|
|
session = sessionmaker(bind=engine)()
|
|
result = session.query(ContractStructure).filter_by(attribute='expiryTime').all()
|
|
session.close()
|
|
if result:
|
|
#now parse the expiry time in python
|
|
expirytime = result[0].value.strip()
|
|
expirytime_split = expirytime.split(' ')
|
|
parse_string = '{}/{}/{} {}'.format( expirytime_split[3], parsing.months[expirytime_split[1]], expirytime_split[2], expirytime_split[4])
|
|
expirytime_object = parsing.arrow.get(parse_string, 'YYYY/M/D HH:mm:ss').replace(tzinfo=expirytime_split[5][3:])
|
|
blocktime_object = parsing.arrow.get(blockinfo['time']).to('Asia/Kolkata')
|
|
|
|
if blocktime_object > expirytime_object:
|
|
print('Contract has expired and will not accept any user participation')
|
|
pushData_SSEapi('Error| Active smart contract of the name {}-{} doesn\t exist at transaction {}'.format(parsed_data['contractName'], outputlist[0], transaction_data['txid']))
|
|
return 0
|
|
|
|
|
|
# Check if exitcondition exists as part of contractstructure and is given in right format
|
|
engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True)
|
|
connection = engine.connect()
|
|
|
|
contractAttributes = connection.execute('select attribute, value from contractstructure').fetchall()
|
|
contractAttributes_T = list(zip(*contractAttributes))
|
|
|
|
if 'exitconditions' in contractAttributes_T[0]:
|
|
exitconditions = connection.execute('select id,value from contractstructure where attribute=="exitconditions"').fetchall()
|
|
exitconditions_T = list(zip(*exitconditions))
|
|
if parsed_data['userChoice'] not in list(exitconditions_T[1]):
|
|
print("Wrong userchoice entered\nThis smartContract pariticipation will be rejected")
|
|
pushData_SSEapi('Error| Wrong user choice entered during participation of smart contract of the name {}-{} at transaction {}'.format(parsed_data['contractName'], outputlist[0], transaction_data['txid']))
|
|
return 0
|
|
|
|
# Check if contractAmount is part of the contract structure, and enforce it if it is
|
|
engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True)
|
|
connection = engine.connect()
|
|
contractAmount = connection.execute('select value from contractstructure where attribute=="contractAmount"').fetchall()
|
|
connection.close()
|
|
|
|
if len(contractAmount) != 0:
|
|
if float(contractAmount[0][0]) != float(parsed_data['tokenAmount']):
|
|
print('Token amount being transferred is not part of the contract structure\nThis transaction will be discarded')
|
|
pushData_SSEapi('Error| Token amount being transferred is not part of the contract structure of the name {}-{} at transaction {}'.format(
|
|
parsed_data['contractName'], outputlist[0],
|
|
transaction_data['txid']))
|
|
return 0
|
|
|
|
# Check if the transaction hash already exists in the contract db (Safety check)
|
|
engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]),echo=True)
|
|
connection = engine.connect()
|
|
participantAdd_txhash = connection.execute('select participantAddress, transactionHash from contractparticipants').fetchall()
|
|
participantAdd_txhash_T = list(zip(*participantAdd_txhash))
|
|
|
|
if len(participantAdd_txhash) != 0 and transaction_data['txid'] in list(participantAdd_txhash_T[1]):
|
|
print('Transaction already exists in the db. This is unusual, please check your code')
|
|
pushData_SSEapi('Error | Transaction {} already exists in the participant db. This is unusual, please check your code'.format(
|
|
transaction_data['txid']))
|
|
return 0
|
|
|
|
|
|
# Check if maximum subscription amount has reached
|
|
engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True)
|
|
ContractBase.metadata.create_all(bind=engine)
|
|
session = sessionmaker(bind=engine)()
|
|
result = session.query(ContractStructure).filter_by(attribute='maximumsubscriptionamount').all()
|
|
if result:
|
|
# now parse the expiry time in python
|
|
maximumsubscriptionamount = float(result[0].value.strip())
|
|
amountDeposited = session.query(func.sum(ContractParticipants.tokenAmount)).all()[0][0]
|
|
|
|
if amountDeposited is None:
|
|
amountDeposited = 0
|
|
|
|
if amountDeposited >= maximumsubscriptionamount:
|
|
print('Maximum subscription amount reached\n Money will be refunded')
|
|
pushData_SSEapi('Error | Maximum subscription amount reached for contract {}-{} at transaction {}. Token will not be transferred'.format(parsed_data['contractName'], outputlist[0],
|
|
transaction_data['txid']))
|
|
return 0
|
|
else:
|
|
if parsed_data['tokenAmount'] + amountDeposited <= maximumsubscriptionamount:
|
|
# Check if the tokenAmount being transferred exists in the address & do the token transfer
|
|
returnval = transferToken(parsed_data['tokenIdentification'], parsed_data['tokenAmount'], inputlist[0], outputlist[0], transaction_data)
|
|
if returnval is not None:
|
|
# Store participant details in the smart contract's db
|
|
session.add(ContractParticipants(participantAddress=inputadd, tokenAmount=parsed_data['tokenAmount'], userChoice=parsed_data['userChoice'], transactionHash=transaction_data['txid'] ))
|
|
session.commit()
|
|
session.close()
|
|
|
|
# Store a mapping of participant address -> Contract participated in
|
|
engine = create_engine('sqlite:///system.db', echo=True)
|
|
SystemBase.metadata.create_all(bind=engine)
|
|
session = sessionmaker(bind=engine)()
|
|
session.add(ContractParticipantMapping(participantAddress=inputadd, tokenAmount=parsed_data['tokenAmount'],
|
|
contractName = parsed_data['contractName'], contractAddress = outputlist[0], transactionHash=transaction_data['txid']))
|
|
session.commit()
|
|
|
|
updateLatestTransaction(transaction_data, parsed_data, blockinfo)
|
|
return
|
|
|
|
else:
|
|
print("Something went wrong in the smartcontract token transfer method")
|
|
return 0
|
|
else:
|
|
# Transfer only part of the tokens users specified, till the time it reaches maximumamount
|
|
returnval = transferToken(parsed_data['tokenIdentification'], maximumsubscriptionamount-amountDeposited,
|
|
inputlist[0], outputlist[0], transaction_data)
|
|
if returnval is not None:
|
|
# Store participant details in the smart contract's db
|
|
session.add(ContractParticipants(participantAddress=inputadd,
|
|
tokenAmount=maximumsubscriptionamount-amountDeposited,
|
|
userChoice=parsed_data['userChoice'], transactionHash=transaction_data['txid']))
|
|
session.commit()
|
|
session.close()
|
|
|
|
# Store a mapping of participant address -> Contract participated in
|
|
engine = create_engine('sqlite:///system.db', echo=True)
|
|
SystemBase.metadata.create_all(bind=engine)
|
|
session = sessionmaker(bind=engine)()
|
|
session.add(ContractParticipantMapping(participantAddress=inputadd,
|
|
tokenAmount=maximumsubscriptionamount-amountDeposited,
|
|
contractName=parsed_data['contractName'], contractAddress = outputlist[0], transactionHash=transaction_data['txid']))
|
|
session.commit()
|
|
session.close()
|
|
updateLatestTransaction(transaction_data, parsed_data, blockinfo)
|
|
return
|
|
|
|
else:
|
|
print("Something went wrong in the smartcontract token transfer method")
|
|
return 0
|
|
|
|
###############################
|
|
# Check if the tokenAmount being transferred exists in the address & do the token transfer
|
|
returnval = transferToken(parsed_data['tokenIdentification'], parsed_data['tokenAmount'],
|
|
inputlist[0], outputlist[0], transaction_data)
|
|
if returnval is not None:
|
|
# Store participant details in the smart contract's db
|
|
session.add(ContractParticipants(participantAddress=inputadd,
|
|
tokenAmount=parsed_data['tokenAmount'],
|
|
userChoice=parsed_data['userChoice'], transactionHash=transaction_data['txid']))
|
|
session.commit()
|
|
session.close()
|
|
|
|
# Store a mapping of participant address -> Contract participated in
|
|
engine = create_engine('sqlite:///system.db', echo=True)
|
|
SystemBase.metadata.create_all(bind=engine)
|
|
session = sessionmaker(bind=engine)()
|
|
session.add(ContractParticipantMapping(participantAddress=inputadd,
|
|
tokenAmount=parsed_data['tokenAmount'],
|
|
contractName=parsed_data['contractName'],
|
|
contractAddress=outputlist[0], transactionHash=transaction_data['txid']))
|
|
session.commit()
|
|
|
|
updateLatestTransaction(transaction_data, parsed_data, blockinfo)
|
|
|
|
pushData_SSEapi('Participation | Succesfully participated in the contract {}-{} at transaction {}'.format(
|
|
parsed_data['contractName'], outputlist[0],
|
|
transaction_data['txid']))
|
|
|
|
return
|
|
|
|
|
|
# todo Rule 47 - If the parsed data type is token incorporation, then check if the name hasn't been taken already
|
|
# if it has been taken then reject the incorporation. Else incorporate it
|
|
elif parsed_data['type'] == 'tokenIncorporation':
|
|
if not os.path.isfile('./tokens/{}.db'.format(parsed_data['tokenIdentification'])):
|
|
engine = create_engine('sqlite:///tokens/{}.db'.format(parsed_data['tokenIdentification']), echo=True)
|
|
Base.metadata.create_all(bind=engine)
|
|
session = sessionmaker(bind=engine)()
|
|
session.add(ActiveTable(address=inputlist[0], parentid=0, transferBalance=parsed_data['tokenAmount']))
|
|
string = "{} getblock {}".format(localapi, transaction_data['blockhash'])
|
|
response = subprocess.check_output(string, shell=True)
|
|
block_data = json.loads(response.decode("utf-8"))
|
|
session.add(TransferLogs(sourceFloAddress=inputadd, destFloAddress=outputlist[0], transferAmount=parsed_data['tokenAmount'], sourceId=0, destinationId=1, blockNumber=block_data['height'], time=block_data['time'], transactionHash=transaction_data['txid']))
|
|
blockchainReference = neturl + 'tx/' + transaction_data['txid']
|
|
session.add(TransactionHistory(sourceFloAddress=inputadd, destFloAddress='-',
|
|
transferAmount=parsed_data['tokenAmount'], blockNumber=block_data['height'],
|
|
time=block_data['time'],
|
|
transactionHash=transaction_data['txid'],
|
|
blockchainReference=blockchainReference))
|
|
session.commit()
|
|
session.close()
|
|
|
|
# add it to token address to token mapping db table
|
|
engine = create_engine('sqlite:///system.db'.format(parsed_data['tokenIdentification']), echo=True)
|
|
connection = engine.connect()
|
|
connection.execute(
|
|
'INSERT INTO tokenAddressMapping (tokenAddress, token, transactionHash) VALUES ("{}", "{}", "{}");'.format(
|
|
inputadd, parsed_data['tokenIdentification'], transaction_data['txid']))
|
|
|
|
connection.close()
|
|
|
|
updateLatestTransaction(transaction_data, parsed_data, blockinfo)
|
|
|
|
pushData_SSEapi('Token | Succesfully incorporated token {} at transaction {}'.format(
|
|
parsed_data['tokenIdentification'], transaction_data['txid']))
|
|
else:
|
|
print('Transaction rejected as the token with same name has already been incorporated')
|
|
pushData_SSEapi('Error | Token incorporation rejected at transaction {} as token {} already exists'.format(transaction_data['txid'], parsed_data['tokenIdentification']))
|
|
return 0
|
|
|
|
# todo Rule 48 - If the parsed data type if smart contract incorporation, then check if the name hasn't been taken already
|
|
# if it has been taken then reject the incorporation.
|
|
elif parsed_data['type'] == 'smartContractIncorporation':
|
|
engine = create_engine('sqlite:///system.db', echo=True)
|
|
connection = engine.connect()
|
|
contractDetails = connection.execute('select contractName, contractAddress from activecontracts').fetchall()
|
|
connection.close()
|
|
|
|
counter = 0
|
|
# Check if the combination of contract name and address exists
|
|
for contract in contractDetails:
|
|
if contract[0] == parsed_data['contractName'] and contract[1] == parsed_data['contractAddress']:
|
|
counter = counter + 1
|
|
|
|
|
|
if counter == 0:
|
|
# todo Rule 49 - If the contract name hasn't been taken before, check if the contract type is an authorized type by the system
|
|
if parsed_data['contractType'] == 'one-time-event':
|
|
print("Smart contract is of the type one-time-event")
|
|
|
|
# userchoice and payeeAddress conditions cannot come together. Check for it
|
|
if 'userchoices' in parsed_data['contractConditions'] and 'payeeAddress' in parsed_data['contractConditions']:
|
|
print('Both userchoice and payeeAddress provided as part of the Contract conditions\nIncorporation of Smart Contract with the name {} will be rejected'.format(parsed_data['contractName']))
|
|
return 0
|
|
|
|
# todo Rule 50 - Contract address mentioned in flodata field should be same as the receiver FLO address on the output side
|
|
# henceforth we will not consider any flo private key initiated comment as valid from this address
|
|
# Unlocking can only be done through smart contract system address
|
|
if parsed_data['contractAddress'] == inputadd:
|
|
dbName = '{}-{}'.format(parsed_data['contractName'], parsed_data['contractAddress'])
|
|
engine = create_engine('sqlite:///smartContracts/{}.db'.format(dbName), echo=True)
|
|
ContractBase.metadata.create_all(bind=engine)
|
|
session = sessionmaker(bind=engine)()
|
|
session.add(ContractStructure(attribute='contractType', index=0, value=parsed_data['contractType']))
|
|
session.add(ContractStructure(attribute='contractName', index=0, value=parsed_data['contractName']))
|
|
session.add(
|
|
ContractStructure(attribute='tokenIdentification', index=0, value=parsed_data['tokenIdentification']))
|
|
session.add(
|
|
ContractStructure(attribute='contractAddress', index=0, value=parsed_data['contractAddress']))
|
|
session.add(
|
|
ContractStructure(attribute='flodata', index=0,
|
|
value=parsed_data['flodata']))
|
|
session.add(
|
|
ContractStructure(attribute='expiryTime', index=0,
|
|
value=parsed_data['contractConditions']['expiryTime']))
|
|
if 'contractAmount' in parsed_data['contractConditions']:
|
|
session.add(
|
|
ContractStructure(attribute='contractAmount', index=0,
|
|
value=parsed_data['contractConditions']['contractAmount']))
|
|
|
|
if 'minimumsubscriptionamount' in parsed_data['contractConditions']:
|
|
session.add(
|
|
ContractStructure(attribute='minimumsubscriptionamount', index=0,
|
|
value=parsed_data['contractConditions']['minimumsubscriptionamount']))
|
|
if 'maximumsubscriptionamount' in parsed_data['contractConditions']:
|
|
session.add(
|
|
ContractStructure(attribute='maximumsubscriptionamount', index=0,
|
|
value=parsed_data['contractConditions']['maximumsubscriptionamount']))
|
|
if 'userchoices' in parsed_data['contractConditions']:
|
|
for key, value in parsed_data['contractConditions']['userchoices'].items():
|
|
session.add(ContractStructure(attribute='exitconditions', index=key, value=value))
|
|
|
|
elif 'payeeAddress' in parsed_data['contractConditions']:
|
|
# in this case, expirydate( or maximumamount) is the trigger internally. Keep a track of expiry dates
|
|
session.add(
|
|
ContractStructure(attribute='payeeAddress', index=0,
|
|
value=parsed_data['contractConditions']['payeeAddress']))
|
|
else:
|
|
print('Neither userchoice nor payeeAddress provided as part of Smart Contract incorporation of the name {}\n This contract incorporation will be rejected'.format(parsed_data['contractName']))
|
|
return 0
|
|
|
|
session.commit()
|
|
session.close()
|
|
|
|
# Store smart contract address in system's db, to be ignored during future transfers
|
|
engine = create_engine('sqlite:///system.db', echo=True)
|
|
SystemBase.metadata.create_all(bind=engine)
|
|
session = sessionmaker(bind=engine)()
|
|
session.add(ActiveContracts(contractName=parsed_data['contractName'],
|
|
contractAddress=parsed_data['contractAddress'], status='active', transactionHash=transaction_data['txid'], incorporationDate=blockinfo['time']))
|
|
session.commit()
|
|
session.close()
|
|
|
|
updateLatestTransaction(transaction_data, parsed_data, blockinfo)
|
|
|
|
pushData_SSEapi('Contract | Contract incorporated at transaction {} with name {}-{}'.format(
|
|
transaction_data['txid'], parsed_data['contractName'], parsed_data['contractAddress']))
|
|
else:
|
|
print('Contract Incorporation rejected as address in Flodata and input address are different')
|
|
pushData_SSEapi('Error | Contract Incorporation rejected as address in Flodata and input address are different at transaction {}'.format(
|
|
transaction_data['txid']))
|
|
return 0
|
|
|
|
|
|
else:
|
|
print('Transaction rejected as a smartcontract with same name is active currently')
|
|
url = 'https://ranchimallflo.duckdns.org/'
|
|
headers = {'Accept': 'application/json', 'Content-Type': 'application/json'}
|
|
r = requests.post(url, json={
|
|
'message': 'Error | Contract Incorporation rejected as a smartcontract with same name {}-{} is active currentlyt at transaction {}'.format(parsed_data['contractName'], parsed_data['contractAddress'], transaction_data['txid'])}, headers=headers)
|
|
return 0
|
|
|
|
elif parsed_data['type'] == 'smartContractPays':
|
|
print('Found a transaction of the type smartContractPays')
|
|
|
|
# Check if input address is a committee address
|
|
if inputlist[0] in committeeAddressList:
|
|
|
|
# Check if the output address is an active Smart contract address
|
|
engine = create_engine('sqlite:///system.db', echo=True)
|
|
connection = engine.connect()
|
|
# todo : Get only activeContracts which have non-local trigger ie. committee triggers them
|
|
|
|
contractDetails = connection.execute('select contractName, contractAddress from activecontracts where status=="expired"').fetchall()
|
|
connection.close()
|
|
contractList = []
|
|
|
|
counter = 0
|
|
for contract in contractDetails:
|
|
if contract[0] == parsed_data['contractName'] and contract[1] == outputlist[0]:
|
|
counter = counter + 1
|
|
|
|
if counter != 1:
|
|
print('Active Smart contract with the given name doesn\'t exist\n This committee trigger will be rejected')
|
|
return 0
|
|
|
|
# Check if the contract has maximumsubscriptionamount and if it has reached it
|
|
engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True)
|
|
connection = engine.connect()
|
|
# todo : filter activeContracts which only have local triggers
|
|
contractStructure = connection.execute('select * from contractstructure').fetchall()
|
|
contractStructure_T = list(zip(*contractStructure))
|
|
|
|
if 'maximumsubscriptionamount' in list(contractStructure_T[1]):
|
|
maximumsubscriptionamount = connection.execute('select value from contractstructure where attribute=="maximumsubscriptionamount"').fetchall()[0][0]
|
|
tokenAmount_sum = connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0]
|
|
if tokenAmount_sum >= maximumsubscriptionamount:
|
|
# Trigger the contract
|
|
contractWinners = connection.execute(
|
|
'select * from contractparticipants where userChoice="{}"'.format(
|
|
parsed_data['triggerCondition'])).fetchall()
|
|
tokenSum = connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0]
|
|
winnerSum = connection.execute(
|
|
'select sum(tokenAmount) from contractparticipants where userChoice="{}"'.format(
|
|
parsed_data['triggerCondition'])).fetchall()[0][0]
|
|
tokenIdentification = connection.execute(
|
|
'select value from contractstructure where attribute="tokenIdentification"').fetchall()[0][0]
|
|
|
|
for winner in contractWinners:
|
|
winnerAmount = "%.8f" % ((winner[2] / winnerSum) * tokenSum)
|
|
returnval = transferToken(tokenIdentification, winnerAmount,
|
|
outputlist[0], winner[1], transaction_data)
|
|
if returnval is None:
|
|
print("CRITICAL ERROR | Something went wrong in the token transfer method while doing local Smart Contract Trigger")
|
|
return 0
|
|
connection.execute(
|
|
'update contractparticipants set winningAmount="{}" where participantAddress="{}" and transactionHash="{}"'.format(
|
|
(winnerAmount, winner[1], winner[4])))
|
|
engine = create_engine('sqlite:///system.db', echo=True)
|
|
connection = engine.connect()
|
|
connection.execute(
|
|
'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format(parsed_data['contractName'], outputlist[0]))
|
|
connection.execute(
|
|
'update activecontracts set closeDate="{}" where contractName="{}" and contractAddress="{}"'.format(block_data['time'],
|
|
parsed_data['contractName'], outputlist[0]))
|
|
connection.close()
|
|
|
|
updateLatestTransaction(transaction_data, parsed_data, blockinfo)
|
|
|
|
pushData_SSEapi('Trigger | Contract triggered of the name {}-{} is active currentlyt at transaction {}'.format(parsed_data['contractName'], outputlist[0], transaction_data['txid']))
|
|
return
|
|
|
|
|
|
# Check if contract has passed expiry time
|
|
expiryTime = connection.execute('select value from contractstructure where attribute=="expiryTime"').fetchall()[0][0]
|
|
expirytime_split = expiryTime.split(' ')
|
|
parse_string = '{}/{}/{} {}'.format(expirytime_split[3], parsing.months[expirytime_split[1]],
|
|
expirytime_split[2], expirytime_split[4])
|
|
expirytime_object = parsing.arrow.get(parse_string, 'YYYY/M/D HH:mm:ss').replace(
|
|
tzinfo=expirytime_split[5][3:])
|
|
blocktime_object = parsing.arrow.get(blockinfo['time']).to('Asia/Kolkata')
|
|
connection.close()
|
|
|
|
if blocktime_object > expirytime_object:
|
|
# Check if the minimum subscription amount has been reached if it exists as part of the structure
|
|
engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True)
|
|
ContractBase.metadata.create_all(bind=engine)
|
|
session = sessionmaker(bind=engine)()
|
|
result = session.query(ContractStructure).filter_by(attribute='minimumsubscriptionamount').all()
|
|
session.close()
|
|
if result:
|
|
minimumsubscriptionamount = float(result[0].value.strip())
|
|
engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]),
|
|
echo=True)
|
|
ContractBase.metadata.create_all(bind=engine)
|
|
session = sessionmaker(bind=engine)()
|
|
result = session.query(ContractStructure).filter_by(attribute='minimumsubscriptionamount').all()
|
|
amountDeposited = session.query(func.sum(ContractParticipants.tokenAmount)).all()[0][0]
|
|
session.close()
|
|
|
|
if amountDeposited is None:
|
|
amountDeposited = 0
|
|
|
|
if amountDeposited < minimumsubscriptionamount:
|
|
print('Minimum subscription amount hasn\'t been reached\n The token will be returned back')
|
|
# Initialize payback to contract participants
|
|
engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]),
|
|
echo=True)
|
|
connection = engine.connect()
|
|
contractParticipants = connection.execute(
|
|
'select participantAddress, tokenAmount, transactionHash from contractparticipants').fetchall()[0][0]
|
|
|
|
for participant in contractParticipants:
|
|
tokenIdentification = connection.execute(
|
|
'select * from contractstructure where attribute="tokenIdentification"').fetchall()[0][
|
|
0]
|
|
contractAddress = connection.execute(
|
|
'select * from contractstructure where attribute="contractAddress"').fetchall()[0][0]
|
|
returnval = transferToken(tokenIdentification, participant[1], contractAddress,
|
|
participant[0], transaction_data)
|
|
if returnval is None:
|
|
print(
|
|
"CRITICAL ERROR | Something went wrong in the token transfer method while doing local Smart Contract Trigger")
|
|
return 0
|
|
|
|
connection.execute(
|
|
'update contractparticipants set winningAmount="{}" where participantAddress="{}" and transactionHash="{}"'.format(
|
|
(participant[1], participant[0], participant[4])))
|
|
|
|
engine = create_engine('sqlite:///system.db', echo=True)
|
|
connection = engine.connect()
|
|
connection.execute(
|
|
'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format(
|
|
parsed_data['contractName'], outputlist[0]))
|
|
connection.execute(
|
|
'update activecontracts set status="{}" where contractName="{}" and contractAddress="{}"'.format(block_data['time'],
|
|
parsed_data['contractName'], outputlist[0]))
|
|
connection.close()
|
|
|
|
pushData_SSEapi('Trigger | Minimum subscription amount not reached at contract {}-{} at transaction {}. Tokens will be refunded'.format(
|
|
parsed_data['contractName'], outputlist[0], transaction_data['txid']))
|
|
return
|
|
|
|
engine = create_engine('sqlite:///smartContracts/{}-{}.db'.format(parsed_data['contractName'], outputlist[0]), echo=True)
|
|
connection = engine.connect()
|
|
contractWinners = connection.execute('select * from contractparticipants where userChoice="{}"'.format(parsed_data['triggerCondition'])).fetchall()
|
|
tokenSum = connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0]
|
|
winnerSum = connection.execute('select sum(tokenAmount) from contractparticipants where userChoice="{}"'.format(parsed_data['triggerCondition'])).fetchall()[0][0]
|
|
tokenIdentification = connection.execute('select value from contractstructure where attribute="tokenIdentification"').fetchall()[0][0]
|
|
|
|
for winner in contractWinners:
|
|
winner = list(winner)
|
|
winnerAmount = "%.8f" % ((winner[2]/winnerSum)*tokenSum)
|
|
returnval = transferToken(tokenIdentification, winnerAmount, outputlist[0], winner[1], transaction_data)
|
|
if returnval is None:
|
|
print(
|
|
"CRITICAL ERROR | Something went wrong in the token transfer method while doing local Smart Contract Trigger")
|
|
return 0
|
|
connection.execute('update contractparticipants set winningAmount="{}" where participantAddress="{}" and transactionHash="{}"'.format(winnerAmount, winner[1], winner[4]))
|
|
connection.close()
|
|
|
|
engine = create_engine('sqlite:///system.db', echo=True)
|
|
connection = engine.connect()
|
|
connection.execute(
|
|
'update activecontracts set status="closed" where contractName="{}" and contractAddress="{}"'.format(
|
|
parsed_data['contractName'], outputlist[0]))
|
|
connection.execute(
|
|
'update activecontracts set closeDate="{}" where contractName="{}" and contractAddress="{}"'.format(block_data['time'],
|
|
parsed_data['contractName'], outputlist[0]))
|
|
connection.close()
|
|
|
|
updateLatestTransaction(transaction_data, parsed_data, blockinfo)
|
|
|
|
pushData_SSEapi('Trigger | Contract triggered of the name {}-{} is active currentlyt at transaction {}'.format(
|
|
parsed_data['contractName'], outputlist[0], transaction_data['txid']))
|
|
|
|
else:
|
|
print('Input address is not part of the committee address list. This trigger is rejected')
|
|
pushData_SSEapi('Error | Smart contract pay\'s input address is not part of the committee address. Contract will be rejected'.format(parsed_data['contractName'], outputlist[0], transaction_data['txid']))
|
|
|
|
|
|
|
|
# todo Rule 1 - Read command line arguments to reset the databases as blank
|
|
# Rule 2 - Read config to set testnet/mainnet
|
|
# Rule 3 - Set flo blockexplorer location depending on testnet or mainnet
|
|
# Rule 4 - Set the local flo-cli path depending on testnet or mainnet
|
|
# Rule 5 - Set the block number to scan from
|
|
|
|
|
|
# Read command line arguments
|
|
parser = argparse.ArgumentParser(description='Script tracks RMT using FLO data on the FLO blockchain - https://flo.cash')
|
|
parser.add_argument('-r', '--reset', nargs='?', const=1, type=int, help='Purge existing db and rebuild it')
|
|
args = parser.parse_args()
|
|
|
|
apppath = os.path.dirname(os.path.realpath(__file__))
|
|
dirpath = os.path.join(apppath, 'tokens')
|
|
if not os.path.isdir(dirpath):
|
|
os.mkdir(dirpath)
|
|
dirpath = os.path.join(apppath, 'smartContracts')
|
|
if not os.path.isdir(dirpath):
|
|
os.mkdir(dirpath)
|
|
|
|
# Read configuration
|
|
config = configparser.ConfigParser()
|
|
config.read('config.ini')
|
|
|
|
# Assignment the flo-cli command
|
|
if config['DEFAULT']['NET'] == 'mainnet':
|
|
neturl = 'https://flosight.duckdns.org/'
|
|
localapi = config['DEFAULT']['FLO_CLI_PATH']
|
|
elif config['DEFAULT']['NET'] == 'testnet':
|
|
neturl = 'https://testnet-flosight.duckdns.org/'
|
|
localapi = '{} --testnet'.format(config['DEFAULT']['FLO_CLI_PATH'])
|
|
else:
|
|
print("NET parameter is wrong\nThe script will exit now ")
|
|
|
|
|
|
# Delete database and smartcontract directory if reset is set to 1
|
|
if args.reset == 1:
|
|
apppath = os.path.dirname(os.path.realpath(__file__))
|
|
dirpath = os.path.join(apppath, 'tokens')
|
|
shutil.rmtree(dirpath)
|
|
os.mkdir(dirpath)
|
|
dirpath = os.path.join(apppath, 'smartContracts')
|
|
shutil.rmtree(dirpath)
|
|
os.mkdir(dirpath)
|
|
dirpath = os.path.join(apppath, 'system.db')
|
|
if os.path.exists(dirpath):
|
|
os.remove(dirpath)
|
|
dirpath = os.path.join(apppath, 'latestCache.db')
|
|
if os.path.exists(dirpath):
|
|
os.remove(dirpath)
|
|
|
|
# Read start block no
|
|
startblock = int(config['DEFAULT']['START_BLOCK'])
|
|
engine = create_engine('sqlite:///system.db', echo=True)
|
|
SystemBase.metadata.create_all(bind=engine)
|
|
session = sessionmaker(bind=engine)()
|
|
session.add( SystemData(attribute='lastblockscanned', value=startblock-1))
|
|
session.commit()
|
|
session.close()
|
|
|
|
# initialize latest cache DB
|
|
engine = create_engine('sqlite:///latestCache.db', echo=True)
|
|
LatestCacheBase.metadata.create_all(bind=engine)
|
|
session.commit()
|
|
session.close()
|
|
|
|
|
|
|
|
|
|
# Read start block no
|
|
engine = create_engine('sqlite:///system.db', echo=True)
|
|
SystemBase.metadata.create_all(bind=engine)
|
|
session = sessionmaker(bind=engine)()
|
|
startblock = int(session.query(SystemData).filter_by(attribute='lastblockscanned').all()[0].value) + 1
|
|
session.commit()
|
|
session.close()
|
|
|
|
|
|
|
|
# todo Rule 6 - Find current block height
|
|
# Rule 7 - Start analysing the block contents from starting block to current height
|
|
|
|
# Find current block height
|
|
string = "{} getblockcount".format(localapi)
|
|
response = subprocess.check_output(string, shell=True)
|
|
current_index = json.loads(response.decode("utf-8"))
|
|
print("current_block_height : " + str(current_index))
|
|
|
|
for blockindex in range( startblock, current_index ):
|
|
processBlock(blockindex)
|
|
|
|
# At this point the script has updated to the latest block
|
|
# Now we connect to flosight's websocket API to get information about the latest blocks
|
|
|
|
sio = socketio.Client()
|
|
sio.connect("https://livenet.flocha.in/socket.io/socket.io.js")
|
|
|
|
|
|
@sio.on('connect')
|
|
def on_connect():
|
|
print('I connected to the websocket')
|
|
sio.emit('subscribe', 'inv')
|
|
|
|
|
|
@sio.on('block')
|
|
def on_block(data):
|
|
print('New block received')
|
|
print(str(data))
|
|
processApiBlock(data) |