Complete Token tracking system

This commit is contained in:
Vivek Teega 2019-04-08 10:39:28 +00:00
parent f49693d71b
commit e88ec4f727
7 changed files with 567 additions and 429 deletions

View File

@ -1,6 +1,4 @@
[DEFAULT]
NET = testnet
FLO_CLI_PATH = /usr/local/bin/flo-cli
START_BLOCK = 498770
START_BLOCK = 525300

View File

@ -5,44 +5,36 @@ Base = declarative_base()
ContractBase = declarative_base()
SystemBase = declarative_base()
class Extra(Base):
__tablename__ = "extra"
id = Column('id', Integer, primary_key=True)
lastblockscanned = Column('lastblockscanned', Integer)
class TransactionHistory(Base):
__tablename__ = "transactionhistory"
id = Column('id', Integer, primary_key=True)
blockno = Column('blockno', Integer)
fromAddress = Column('fromAddress', String)
toAddress = Column('toAddress', String)
amount = Column('amount', Float)
blockchainReference = Column('blockchainReference', String)
class TransactionTable(Base):
__tablename__ = "transactiontable"
class ActiveTable(Base):
__tablename__ = "activeTable"
id = Column('id', Integer, primary_key=True)
address = Column('address', String)
parentid = Column('parentid', Integer)
consumedpid = Column('consumedpid', String)
transferBalance = Column('transferBalance', Float)
class ConsumedTable(Base):
__tablename__ = "consumedTable"
primaryKey = Column('primaryKey', Integer, primary_key=True)
id = Column('id', Integer)
address = Column('address', String)
parentid = Column('parentid', Integer)
consumedpid = Column('consumedpid', String)
transferBalance = Column('transferBalance', Float)
class TransferLogs(Base):
__tablename__ = "transferlogs"
id = Column('id', Integer, primary_key=True)
primaryIDReference = Column('primaryIDReference', Integer)
transferDescription = Column('transferDescription', String)
transferIDConsumed = Column('transferIDConsumed', Integer)
blockchainReference = Column('blockchainReference', String)
class Webtable(Base):
__tablename__ = "webtable"
id = Column('id', Integer, primary_key=True)
transferDescription = Column('transferDescription', String)
primary_key = Column('id', Integer, primary_key=True)
sourceFloAddress = Column('sourceFloAddress', String)
destFloAddress = Column('destFloAddress', String)
transferAmount = Column('transferAmount', Float)
sourceId = Column('sourceId', Integer)
destinationId = Column('destinationId', Integer)
blockNumber = Column('blockNumber', Integer)
time = Column('time', Integer)
blockchainReference = Column('blockchainReference', String)
class ContractStructure(ContractBase):
@ -59,7 +51,7 @@ class ContractParticipants(ContractBase):
id = Column('id', Integer, primary_key=True)
participantAddress = Column('participantAddress', String)
tokenAmount = Column('tokenAmount', Float)
contractCondition = Column('contractCondition', String)
userPreference = Column('userPreference', String)
class ActiveContracts(SystemBase):
__tablename__ = "activecontracts"
@ -68,3 +60,10 @@ class ActiveContracts(SystemBase):
contractName = Column('contractName', String)
contractAddress = Column('contractAddress', String)
class SystemData(SystemBase):
__tablename__ = "systemData"
id = Column('id', Integer, primary_key=True)
attribute = Column('attribute', String)
value = Column('value', String)

View File

@ -29,7 +29,7 @@ def isSmartContract(text):
for word in textList:
if word == '':
continue
if word.endswith('@'):
if word.endswith('@') and len(word) != 1:
return word
return False
@ -74,7 +74,7 @@ def extractMarker(text):
for word in textList:
if word == '':
continue
if word.endswith('#'):
if word.endswith('#') and len(word) != 1:
return word
return False
@ -82,15 +82,27 @@ def extractMarker(text):
def extractInitTokens(text):
base_units = {'thousand':10**3 , 'million':10**6 ,'billion':10**9, 'trillion':10**12}
textList = text.split(' ')
counter = 0
value = None
for idx,word in enumerate(textList):
try:
result = float(word)
if textList[idx+1] in base_units:
return result*base_units[textList[idx+1]]
return result
except:
continue
return None
for unit in base_units:
result = word.split(unit)
if len(result) == 1:
try:
result = float(word)
if textList[idx+1] in base_units:
value = result*base_units[textList[idx+1]]
counter = counter + 1
except:
continue
elif len(result) == 2 and result[1]=='':
value = float(result[0])*base_units[unit]
counter = counter + 1
if counter == 1:
return value
else:
return None
def extractAddress(text):
@ -98,7 +110,7 @@ def extractAddress(text):
for word in textList:
if word == '':
continue
if word[-1] == '$':
if word[-1] == '$' and len(word) != 1:
return word
return None
@ -145,6 +157,10 @@ def extractContractConditions(text, contracttype, marker):
extractedRules['smartcontractpays'][idx] = condition.strip()
else:
print("something is wrong with smartcontractpays conditions")
elif rule[:10]=='expirytime':
pattern = re.compile('[^expirytime="].*[^"]')
searchResult = pattern.search(rule).group(0)
extractedRules['expirytime'] = searchResult
if 'userassetcommitment' in extractedRules and 'smartcontractpays' in extractedRules:
return extractedRules
@ -164,37 +180,55 @@ def extractTriggerCondition(text):
# Combine test
def parse_flodata(string):
# todo Rule 20 - remove 'text:' from the start of flodata if it exists
if string[0:5] == 'text:':
string = string.split('text:')[1]
# todo Rule 21 - Collapse multiple spaces into a single space in the whole of flodata
# todo Rule 22 - convert flodata to lowercase to make the system case insensitive
nospacestring = re.sub(' +', ' ', string)
cleanstring = nospacestring.lower()
# todo Rule 23 - Count number of words ending with @ and #
atList = []
hashList = []
for word in cleanstring.split(' '):
if word.endswith('@'):
if word.endswith('@') and len(word) != 1:
atList.append(word)
if word.endswith('#'):
if word.endswith('#') and len(word) != 1:
hashList.append(word)
# todo Rule 24 - Reject the following conditions - a. number of # & number of @ is equal to 0 then reject
# todo Rule 25 - If number of # or number of @ is greater than 1, reject
# todo Rule 25.a - If a transaction is rejected, it means parsed_data type is noise
# Filter noise first - check if the words end with either @ or #
if (len(atList)==0 and len(hashList)==0) or len(atList)>1 or len(hashList)>1:
parsed_data = {'type': 'noise'}
# todo Rule 26 - if number of # is 1 and number of @ is 0, then check if its token creation or token transfer transaction
elif len(hashList)==1 and len(atList)==0:
# Passing the above check means token creation or transfer
incorporation = isIncorp(cleanstring)
transfer = isTransfer(cleanstring)
if incorporation and not transfer:
# todo Rule 27 - if (neither token incorporation and token transfer) OR both token incorporation and token transfer, reject
if (not incorporation and not transfer) or (incorporation and transfer):
parsed_data = {'type': 'noise'}
# todo Rule 28 - if token creation and not token transfer then it is confirmed that is it a token creation transaction
# todo Rule 29 - Extract total number of tokens issued, if its not mentioned then reject
elif incorporation and not transfer:
initTokens = extractInitTokens(cleanstring)
if initTokens is not None:
parsed_data = {'type': 'tokenIncorporation', 'flodata': string, 'tokenIdentification': hashList[0][:-1],
'tokenAmount': initTokens}
else:
parsed_data = {'type': 'noise'}
# todo Rule 30 - if not token creation and is token transfer then then process it for token transfer rules
# todo Rule 31 - Extract number of tokens to be sent and the address to which to be sent, both data is mandatory
elif not incorporation and transfer:
amount = extractAmount(cleanstring, hashList[0][:-1])
address = extractAddress(nospacestring)
@ -204,15 +238,19 @@ def parse_flodata(string):
'tokenAmount': amount, 'address': address[:-1]}
else:
parsed_data = {'type': 'noise'}
else:
parsed_data = {'type': 'noise'}
# todo Rule 32 - if number of # is 1 and number of @ is 1, then process for smart contract transfer or creation
elif len(hashList)==1 and len(atList)==1:
# Passing the above check means Smart Contract creation or transfer
incorporation = isIncorp(cleanstring)
transfer = isTransfer(cleanstring)
if incorporation and not transfer:
# todo Rule 33 - if a confusing smart contract command is given, like creating and sending at the same time, or no
if (not incorporation and not transfer) or (incorporation and transfer):
parsed_data = {'type': 'noise'}
# todo Rule 34 - if incorporation and not transfer, then extract type of contract, address of the contract and conditions of the contract. Reject if any of those is not present
elif incorporation and not transfer:
contracttype = extractContractType(cleanstring)
contractaddress = extractAddress(nospacestring)
contractconditions = extractContractConditions(cleanstring, 'betting*', marker)
@ -224,29 +262,31 @@ def parse_flodata(string):
'contractConditions': contractconditions}
else:
parsed_data = {'type': 'noise'}
# todo Rule 35 - if it is not incorporation and it is transfer, then extract smart contract amount to be locked and userPreference. If any of them is missing, then reject
elif not incorporation and transfer:
# We are at the send/transfer of smart contract
amount = extractAmount(cleanstring, hashList[0][:-1])
contractcondition = extractContractCondition(cleanstring)
if None not in [amount, contractcondition]:
userPreference = extractContractCondition(cleanstring)
if None not in [amount, userPreference]:
parsed_data = {'type': 'transfer', 'transferType': 'smartContract', 'flodata': string,
'tokenIdentification': hashList[0][:-1],
'operation': 'transfer', 'tokenAmount': amount, 'contractName': atList[0][:-1],
'contractCondition': contractcondition}
'userPreference': userPreference}
else:
parsed_data = {'type': 'noise'}
else:
parsed_data = {'type': 'noise'}
elif len(hashList)==0 and len(atList)==1:
# todo Rule 36 - Check for only a single @ and the substring "smart contract system says" in flodata, else reject
elif (len(hashList)==0 and len(atList)==1) and 'smart contract system says' in cleanstring:
# Passing the above check means Smart Contract pays | exitcondition triggered from the committee
# todo Rule 37 - Extract the trigger condition given by the committee. If its missing, reject
triggerCondition = extractTriggerCondition(cleanstring)
if triggerCondition is not None:
parsed_data = {'type': 'smartContractPays', 'contractName': atList[0][:-1], 'triggerCondition': triggerCondition.group().strip()[1:-1]}
else:
parsed_data = {'type':'noise'}
else:
parsed_data = {'type': 'noise'}
return parsed_data
result = parse_flodata("for contractname@ Smart Contract System says 'NAMO=WIN'")
print(result)
return parsed_data

BIN
system.db Normal file

Binary file not shown.

BIN
tokens/rmt.db Normal file

Binary file not shown.

View File

@ -1,369 +0,0 @@
import requests
import json
import sqlite3
import argparse
import configparser
import subprocess
import sys
import parsing
import os
import shutil
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy import create_engine, func, desc
from models import Extra, TransactionHistory, TransactionTable, TransferLogs, Webtable, Base, ContractStructure, ContractBase, ContractParticipants, SystemBase, ActiveContracts
committeeAddressList = ['oUc4dVvxwK7w5MHUHtev8UawN3eDjiZnNx']
def transferToken(tokenIdentification, tokenAmount, inputAddress, outputAddress):
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(TransactionTable.transferBalance)).filter_by(address=inputAddress).all()[0][0]
commentTransferAmount = tokenAmount
if availableTokens is None:
print("The input address dosen't exist in our database ")
session.close()
return None
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 None
elif availableTokens >= commentTransferAmount:
table = session.query(TransactionTable).filter(TransactionTable.address==inputAddress, TransactionTable.transferBalance>0).all()
pidlst = []
checksum = 0
for row in table:
if checksum >= commentTransferAmount:
break
pidlst.append(row.id)
checksum = checksum + row.transferBalance
balance = commentTransferAmount
opbalance = session.query(func.sum(TransactionTable.transferBalance)).filter_by(address=outputAddress).all()[0][0]
if opbalance is None:
opbalance = 0
ipbalance = availableTokens
for pid in pidlst:
temp = session.query(TransactionTable.transferBalance).filter_by(id=pid).all()[0][0]
if balance <= temp:
session.add(TransactionTable(address=outputAddress, parentid=pid, transferBalance=balance))
entry = session.query(TransactionTable).filter(TransactionTable.id == pid).all()
entry[0].transferBalance = temp - balance
session.commit()
## transaction logs section ##
result = session.query(TransactionTable.id).order_by(desc(TransactionTable.id)).all()
lastid = result[-1].id
transferDescription = str(balance) + " tokens transferred to " + str(
outputAddress) + " from " + str(inputAddress)
blockchainReference = '{}tx/{}'.format(neturl, str(transaction))
session.add(TransferLogs(primaryIDReference=lastid, transferDescription=transferDescription,
transferIDConsumed=pid, blockchainReference=blockchainReference))
transferDescription = str(inputAddress) + " balance UPDATED from " + str(
temp) + " to " + str(temp - balance)
blockchainReference = '{}tx/{}'.format(neturl, str(transaction))
session.add(TransferLogs(primaryIDReference=pid, transferDescription=transferDescription,
blockchainReference=blockchainReference))
## transaction history table ##
session.add(TransactionHistory(blockno=blockindex, fromAddress=inputAddress,
toAddress=outputAddress, amount=str(balance),
blockchainReference=blockchainReference))
##webpage table section ##
transferDescription = str(commentTransferAmount) + " tokens transferred from " + str(
inputAddress) + " to " + str(outputAddress)
session.add(
Webtable(transferDescription=transferDescription, blockchainReference=blockchainReference))
transferDescription = "UPDATE " + str(outputAddress) + " balance from " + str(
opbalance) + " to " + str(opbalance + commentTransferAmount)
session.add(
Webtable(transferDescription=transferDescription, blockchainReference=blockchainReference))
transferDescription = "UPDATE " + str(inputAddress) + " balance from " + str(
ipbalance) + " to " + str(ipbalance - commentTransferAmount)
session.add(
Webtable(transferDescription=transferDescription, blockchainReference=blockchainReference))
balance = 0
session.commit()
elif balance > temp:
session.add(TransactionTable(address=outputAddress, parentid=pid, transferBalance=temp))
entry = session.query(TransactionTable).filter(TransactionTable.id == pid).all()
entry[0].transferBalance = 0
session.commit()
##transaction logs section ##
result = session.query(TransactionTable.id).order_by(desc(TransactionTable.id)).all()
lastid = result[-1].id
transferDescription = str(temp) + " tokens transferred to " + str(outputAddress) + " from " + str(inputAddress)
blockchainReference = '{}tx/{}'.format(neturl, str(transaction))
session.add(TransferLogs(primaryIDReference=lastid, transferDescription=transferDescription,
transferIDConsumed=pid, blockchainReference=blockchainReference))
transferDescription = str() + " balance UPDATED from " + str(temp) + " to " + str(0)
blockchainReference = '{}tx/{}'.format(neturl, str(transaction))
session.add(TransferLogs(primaryIDReference=pid, transferDescription=transferDescription,
blockchainReference=blockchainReference))
## transaction history table ##
session.add(TransactionHistory(blockno=blockindex, fromAddress=inputAddress,
toAddress=outputAddress, amount=str(balance),
blockchainReference=blockchainReference))
balance = balance - temp
session.commit()
session.close()
return 1
def startWorking(transaction_data, parsed_data):
# Do the necessary checks for the inputs and outputs
# Create inputlist and outputlist
inputlist = []
querylist = []
for obj in transaction_data["vin"]:
querylist.append([obj["txid"], obj["vout"]])
if len(querylist) > 1:
print("Program has detected more than one input address ")
print("This transaction will be discarded")
return
inputval = 0
inputadd = ''
for query in querylist:
string = "{} getrawtransaction {} 1".format(localapi, str(query[0]))
response = subprocess.check_output(string, shell=True)
content = json.loads(response.decode("utf-8"))
for objec in content["vout"]:
if objec["n"] == query[1]:
inputadd = objec["scriptPubKey"]["addresses"][0]
inputval = inputval + objec["value"]
inputlist = [[inputadd, inputval]]
if len(transaction_data["vout"]) > 2:
print("Program has detected more than one output address ")
print("This transaction will be discarded")
return
outputlist = []
for obj in transaction_data["vout"]:
if obj["scriptPubKey"]["type"] == "pubkeyhash":
if inputlist[0][0] == obj["scriptPubKey"]["addresses"][0]:
continue
outputlist.append([obj["scriptPubKey"]["addresses"][0], obj["value"]])
print("\n\nInput List")
print(inputlist)
print("\nOutput List")
print(outputlist)
# Do operations as per the type of transaction
if parsed_data['type'] == 'transfer':
print('Found a transaction of the type transfer')
if parsed_data['transferType'] == 'token':
returnval = transferToken(parsed_data['tokenIdentification'], parsed_data['tokenAmount'], inputlist[0][0], outputlist[0][0])
if returnval is None:
print("Something went wrong in the token transfer method")
elif parsed_data['transferType'] == 'smartContract':
# Check if the tokenAmount being transferred exists in the address & do the token transfer
returnval = transferToken(parsed_data['tokenIdentification'], parsed_data['tokenAmount'], inputlist[0][0], outputlist[0][0])
if returnval is not None:
# Store participant details in the smart contract's db
engine = create_engine('sqlite:///smartContracts/{}.db'.format(parsed_data['contractName']), echo=True)
Base.metadata.create_all(bind=engine)
session = sessionmaker(bind=engine)()
session.add(ContractParticipants(participantAddress=inputadd, tokenAmount=parsed_data['tokenAmount'],
contractCondition=parsed_data['contractCondition']))
session.commit()
session.close()
else:
print("Something went wrong in the smartcontract token transfer method")
elif parsed_data['type'] == 'tokenIncorporation':
if not os.path.isfile('./tokens/{}.db'.format(parsed_data['tokenIdentification'])):
engine = create_engine('sqlite:///tokens/{}.db'.format(parsed_data['tokenIdentification']), echo=True)
Base.metadata.create_all(bind=engine)
session = sessionmaker(bind=engine)()
session.add(TransactionTable(address=outputlist[0][0], parentid=0, transferBalance=parsed_data['tokenAmount']))
session.commit()
session.close()
else:
print('Transaction rejected as the token with same name has already been incorporated')
elif parsed_data['type'] == 'smartContractIncorporation':
if not os.path.isfile('./smartContracts/{}.db'.format(parsed_data['contractName'])):
if parsed_data['contractType'] == 'betting':
print("Smart contract is of the type betting")
if parsed_data['contractName'] is not None and parsed_data['contractAddress'] == outputlist[0][0]:
print("Hey I have passed the first test for smart contract")
engine = create_engine('sqlite:///smartContracts/{}.db'.format(parsed_data['contractName']), echo=True)
ContractBase.metadata.create_all(bind=engine)
session = sessionmaker(bind=engine)()
session.add(ContractStructure(attribute='contractType', index=0, value=parsed_data['contractType']))
session.add(ContractStructure(attribute='contractName', index=0, value=parsed_data['contractName']))
session.add(
ContractStructure(attribute='tokenIdentification', index=0, value=parsed_data['tokenIdentification']))
session.add(
ContractStructure(attribute='contractAddress', index=0, value=parsed_data['contractAddress']))
session.add(
ContractStructure(attribute='flodata', index=0,
value=parsed_data['flodata']))
session.add(
ContractStructure(attribute='userassetcommitment', index=0,
value=parsed_data['contractConditions']['userassetcommitment'].split(parsed_data['tokenIdentification'][:-1])[0]))
for key, value in parsed_data['contractConditions']['smartcontractpays'].items():
session.add(ContractStructure(attribute='exitconditions', index=key, value=value))
session.commit()
session.close()
# 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']))
session.commit()
session.close()
else:
print('Transaction rejected as a smartcontract with same name has already been incorporated')
elif parsed_data['type'] == 'smartContractPays':
print('Found a transaction of the type smartContractPays')
# Check if input address is a committee address
if inputlist[0][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()
activeContracts = connection.execute('select * from activecontracts').fetchall()
connection.close()
# Change columns into rows - https://stackoverflow.com/questions/44360162/how-to-access-a-column-in-a-list-of-lists-in-python
activeContracts = list(zip(*activeContracts))
if outputlist[0][0] in activeContracts[2] and parsed_data['contractName'] in activeContracts[1]:
engine = create_engine('sqlite:///smartContracts/{}.db'.format(parsed_data['contractName']), echo=True)
connection = engine.connect()
contractWinners = connection.execute('select * from contractparticipants where contractCondition="{}"'.format(parsed_data['triggerCondition'])).fetchall()
tokenSum = connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0]
tokenIdentification = connection.execute('select value from contractstructure where attribute="tokenIdentification"').fetchall()[0][0]
connection.close()
contractWinners = list(zip(*contractWinners))
for address in contractWinners[1]:
transferToken(tokenIdentification, tokenSum/len(contractWinners[1]), outputlist[0][0], address)
else:
print('This trigger doesn\'t apply to an active contract. It will be discarded')
else:
print('Input address is not part of the committee address list. This trigger is rejected')
# Read configuration
config = configparser.ConfigParser()
config.read('config.ini')
# Read command line arguments
parser = argparse.ArgumentParser(description='Script tracks RMT using FLO data on the FLO blockchain - https://flo.cash')
parser.add_argument('-r', '--reset', nargs='?', const=1, type=int, help='Purge existing db and rebuild it')
args = parser.parse_args()
# Assignment the flo-cli command
if config['DEFAULT']['NET'] == 'mainnet':
neturl = 'https://florincoin.info/'
localapi = config['DEFAULT']['FLO_CLI_PATH']
elif config['DEFAULT']['NET'] == 'testnet':
neturl = 'https://testnet.florincoin.info/'
localapi = '{} --testnet'.format(config['DEFAULT']['FLO_CLI_PATH'])
else:
print("NET parameter is wrong\nThe script will exit now ")
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)
# 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)
# Read start block no
startblock = int(config['DEFAULT']['START_BLOCK'])
# Find current block height
string = "{} getblockcount".format(localapi)
response = subprocess.check_output(string, shell=True)
current_index = json.loads(response.decode("utf-8"))
print("current_block_height : " + str(current_index))
for blockindex in range( startblock, current_index ):
print(blockindex)
# Scan every block
string = "{} getblockhash {}".format(localapi, str(blockindex))
response = subprocess.check_output(string, shell=True)
blockhash = response.decode("utf-8")
string = "{} getblock {}".format(localapi, str(blockhash))
response = subprocess.check_output(string, shell=True)
blockinfo = json.loads(response.decode("utf-8"))
# Scan every transaction
for transaction in blockinfo["tx"]:
string = "{} getrawtransaction {} 1".format(localapi, str(transaction))
response = subprocess.check_output(string, shell=True)
transaction_data = json.loads(response.decode("utf-8"))
text = transaction_data["floData"]
if blockindex == 498385:
print('debug point')
parsed_data = parsing.parse_flodata(text)
if parsed_data['type'] != 'noise':
print(blockindex)
print(parsed_data['type'])
startWorking(transaction_data, parsed_data)

View File

@ -0,0 +1,470 @@
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, Base, ContractStructure, ContractBase, ContractParticipants, SystemBase, ActiveContracts
committeeAddressList = ['oUc4dVvxwK7w5MHUHtev8UawN3eDjiZnNx']
def transferToken(tokenIdentification, tokenAmount, inputAddress, outputAddress):
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 = tokenAmount
if availableTokens is None:
print("The input address dosen't exist in our database ")
session.close()
return None
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 None
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'],
blockchainReference=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'],
blockchainReference=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'],
blockchainReference=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()
session.close()
return 1
def startWorking(transaction_data, parsed_data):
# 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')
return
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
# 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
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':
returnval = transferToken(parsed_data['tokenIdentification'], parsed_data['tokenAmount'], inputlist[0], outputlist[0])
if returnval is None:
print("Something went wrong in the token transfer method")
# 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':
# Check if the contract has expired
if parsed_data['expiryTime'] and time.time()>parsed_data['expiryTime']:
print('Contract has expired and will not accept any user participation')
return
# 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])
if returnval is not None:
# Store participant details in the smart contract's db
engine = create_engine('sqlite:///smartContracts/{}.db'.format(parsed_data['contractName']), echo=True)
Base.metadata.create_all(bind=engine)
session = sessionmaker(bind=engine)()
session.add(ContractParticipants(participantAddress=inputadd, tokenAmount=parsed_data['tokenAmount'], userPreference=parsed_data['userPreference']))
session.commit()
session.close()
else:
print("Something went wrong in the smartcontract token transfer method")
# 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=outputlist[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][0], transferAmount=parsed_data['tokenAmount'], sourceId=0, destinationId=1, blockNumber=block_data['height'], time=block_data['time'], blockchainReference=transaction_data['txid']))
session.commit()
session.close()
else:
print('Transaction rejected as the token with same name has already been incorporated')
# 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':
if not os.path.isfile('./smartContracts/{}.db'.format(parsed_data['contractName'])):
# 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'] == 'betting':
print("Smart contract is of the type betting")
# 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'] == outputlist[0]:
print("Hey I have passed the first test for smart contract")
engine = create_engine('sqlite:///smartContracts/{}.db'.format(parsed_data['contractName']), echo=True)
ContractBase.metadata.create_all(bind=engine)
session = sessionmaker(bind=engine)()
session.add(ContractStructure(attribute='contractType', index=0, value=parsed_data['contractType']))
session.add(ContractStructure(attribute='contractName', index=0, value=parsed_data['contractName']))
session.add(
ContractStructure(attribute='tokenIdentification', index=0, value=parsed_data['tokenIdentification']))
session.add(
ContractStructure(attribute='contractAddress', index=0, value=parsed_data['contractAddress']))
session.add(
ContractStructure(attribute='flodata', index=0,
value=parsed_data['flodata']))
session.add(
ContractStructure(attribute='userassetcommitment', index=0,
value=parsed_data['contractConditions']['userassetcommitment'].split(parsed_data['tokenIdentification'][:-1])[0]))
for key, value in parsed_data['contractConditions']['smartcontractpays'].items():
session.add(ContractStructure(attribute='exitconditions', index=key, value=value))
session.commit()
session.close()
# 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']))
session.commit()
session.close()
else:
print('Transaction rejected as a smartcontract with same name has already been incorporated')
elif parsed_data['type'] == 'smartContractPays':
print('Found a transaction of the type smartContractPays')
# 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()
activeContracts = connection.execute('select * from activecontracts').fetchall()
connection.close()
# Change columns into rows - https://stackoverflow.com/questions/44360162/how-to-access-a-column-in-a-list-of-lists-in-python
activeContracts = list(zip(*activeContracts))
if outputlist[0] in activeContracts[2] and parsed_data['contractName'] in activeContracts[1]:
engine = create_engine('sqlite:///smartContracts/{}.db'.format(parsed_data['contractName']), echo=True)
connection = engine.connect()
contractWinners = connection.execute('select * from contractparticipants where userPreference="{}"'.format(parsed_data['triggerCondition'])).fetchall()
tokenSum = connection.execute('select sum(tokenAmount) from contractparticipants').fetchall()[0][0]
tokenIdentification = connection.execute('select value from contractstructure where attribute="tokenIdentification"').fetchall()[0][0]
connection.close()
contractWinners = list(zip(*contractWinners))
for address in contractWinners[1]:
transferToken(tokenIdentification, tokenSum/len(contractWinners[1]), outputlist[0], address)
else:
print('This trigger doesn\'t apply to an active contract. It will be discarded')
else:
print('Input address is not part of the committee address list. This trigger is rejected')
# 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://florincoin.info/'
localapi = config['DEFAULT']['FLO_CLI_PATH']
elif config['DEFAULT']['NET'] == 'testnet':
neturl = 'https://testnet.florincoin.info/'
localapi = '{} --testnet'.format(config['DEFAULT']['FLO_CLI_PATH'])
else:
print("NET parameter is wrong\nThe script will exit now ")
# Delete database and smartcontract directory if reset is set to 1
if args.reset == 1:
apppath = os.path.dirname(os.path.realpath(__file__))
dirpath = os.path.join(apppath, 'tokens')
shutil.rmtree(dirpath)
os.mkdir(dirpath)
dirpath = os.path.join(apppath, 'smartContracts')
shutil.rmtree(dirpath)
os.mkdir(dirpath)
dirpath = os.path.join(apppath, 'system.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()
# 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)
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 ):
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"]
if blockindex == 525362:
print('debug point')
# todo Rule 9 - Reject all noise transactions. Further rules are in parsing.py
parsed_data = parsing.parse_flodata(text)
if parsed_data['type'] != 'noise':
print(blockindex)
print(parsed_data['type'])
startWorking(transaction_data, parsed_data)
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()