flo-token-tracking/tracktokens-smartcontract.py
Vivek Teega f49693d71b Spine of betting smart contract
This commit introduces the betting smart contract on FLO blockchain
2019-03-23 20:49:53 +00:00

369 lines
17 KiB
Python

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)