Complete Token tracking system
This commit is contained in:
parent
f49693d71b
commit
e88ec4f727
@ -1,6 +1,4 @@
|
|||||||
[DEFAULT]
|
[DEFAULT]
|
||||||
NET = testnet
|
NET = testnet
|
||||||
FLO_CLI_PATH = /usr/local/bin/flo-cli
|
FLO_CLI_PATH = /usr/local/bin/flo-cli
|
||||||
START_BLOCK = 498770
|
START_BLOCK = 525300
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
59
models.py
59
models.py
@ -5,44 +5,36 @@ Base = declarative_base()
|
|||||||
ContractBase = declarative_base()
|
ContractBase = declarative_base()
|
||||||
SystemBase = declarative_base()
|
SystemBase = declarative_base()
|
||||||
|
|
||||||
class Extra(Base):
|
class ActiveTable(Base):
|
||||||
__tablename__ = "extra"
|
__tablename__ = "activeTable"
|
||||||
|
|
||||||
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"
|
|
||||||
|
|
||||||
id = Column('id', Integer, primary_key=True)
|
id = Column('id', Integer, primary_key=True)
|
||||||
address = Column('address', String)
|
address = Column('address', String)
|
||||||
parentid = Column('parentid', Integer)
|
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)
|
transferBalance = Column('transferBalance', Float)
|
||||||
|
|
||||||
class TransferLogs(Base):
|
class TransferLogs(Base):
|
||||||
__tablename__ = "transferlogs"
|
__tablename__ = "transferlogs"
|
||||||
|
|
||||||
id = Column('id', Integer, primary_key=True)
|
primary_key = Column('id', Integer, primary_key=True)
|
||||||
primaryIDReference = Column('primaryIDReference', Integer)
|
sourceFloAddress = Column('sourceFloAddress', String)
|
||||||
transferDescription = Column('transferDescription', String)
|
destFloAddress = Column('destFloAddress', String)
|
||||||
transferIDConsumed = Column('transferIDConsumed', Integer)
|
transferAmount = Column('transferAmount', Float)
|
||||||
blockchainReference = Column('blockchainReference', String)
|
sourceId = Column('sourceId', Integer)
|
||||||
|
destinationId = Column('destinationId', Integer)
|
||||||
class Webtable(Base):
|
blockNumber = Column('blockNumber', Integer)
|
||||||
__tablename__ = "webtable"
|
time = Column('time', Integer)
|
||||||
|
|
||||||
id = Column('id', Integer, primary_key=True)
|
|
||||||
transferDescription = Column('transferDescription', String)
|
|
||||||
blockchainReference = Column('blockchainReference', String)
|
blockchainReference = Column('blockchainReference', String)
|
||||||
|
|
||||||
class ContractStructure(ContractBase):
|
class ContractStructure(ContractBase):
|
||||||
@ -59,7 +51,7 @@ class ContractParticipants(ContractBase):
|
|||||||
id = Column('id', Integer, primary_key=True)
|
id = Column('id', Integer, primary_key=True)
|
||||||
participantAddress = Column('participantAddress', String)
|
participantAddress = Column('participantAddress', String)
|
||||||
tokenAmount = Column('tokenAmount', Float)
|
tokenAmount = Column('tokenAmount', Float)
|
||||||
contractCondition = Column('contractCondition', String)
|
userPreference = Column('userPreference', String)
|
||||||
|
|
||||||
class ActiveContracts(SystemBase):
|
class ActiveContracts(SystemBase):
|
||||||
__tablename__ = "activecontracts"
|
__tablename__ = "activecontracts"
|
||||||
@ -68,3 +60,10 @@ class ActiveContracts(SystemBase):
|
|||||||
contractName = Column('contractName', String)
|
contractName = Column('contractName', String)
|
||||||
contractAddress = Column('contractAddress', 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)
|
||||||
|
|
||||||
|
|||||||
94
parsing.py
94
parsing.py
@ -29,7 +29,7 @@ def isSmartContract(text):
|
|||||||
for word in textList:
|
for word in textList:
|
||||||
if word == '':
|
if word == '':
|
||||||
continue
|
continue
|
||||||
if word.endswith('@'):
|
if word.endswith('@') and len(word) != 1:
|
||||||
return word
|
return word
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ def extractMarker(text):
|
|||||||
for word in textList:
|
for word in textList:
|
||||||
if word == '':
|
if word == '':
|
||||||
continue
|
continue
|
||||||
if word.endswith('#'):
|
if word.endswith('#') and len(word) != 1:
|
||||||
return word
|
return word
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@ -82,15 +82,27 @@ def extractMarker(text):
|
|||||||
def extractInitTokens(text):
|
def extractInitTokens(text):
|
||||||
base_units = {'thousand':10**3 , 'million':10**6 ,'billion':10**9, 'trillion':10**12}
|
base_units = {'thousand':10**3 , 'million':10**6 ,'billion':10**9, 'trillion':10**12}
|
||||||
textList = text.split(' ')
|
textList = text.split(' ')
|
||||||
|
counter = 0
|
||||||
|
value = None
|
||||||
for idx,word in enumerate(textList):
|
for idx,word in enumerate(textList):
|
||||||
try:
|
for unit in base_units:
|
||||||
result = float(word)
|
result = word.split(unit)
|
||||||
if textList[idx+1] in base_units:
|
if len(result) == 1:
|
||||||
return result*base_units[textList[idx+1]]
|
try:
|
||||||
return result
|
result = float(word)
|
||||||
except:
|
if textList[idx+1] in base_units:
|
||||||
continue
|
value = result*base_units[textList[idx+1]]
|
||||||
return None
|
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):
|
def extractAddress(text):
|
||||||
@ -98,7 +110,7 @@ def extractAddress(text):
|
|||||||
for word in textList:
|
for word in textList:
|
||||||
if word == '':
|
if word == '':
|
||||||
continue
|
continue
|
||||||
if word[-1] == '$':
|
if word[-1] == '$' and len(word) != 1:
|
||||||
return word
|
return word
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@ -145,6 +157,10 @@ def extractContractConditions(text, contracttype, marker):
|
|||||||
extractedRules['smartcontractpays'][idx] = condition.strip()
|
extractedRules['smartcontractpays'][idx] = condition.strip()
|
||||||
else:
|
else:
|
||||||
print("something is wrong with smartcontractpays conditions")
|
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:
|
if 'userassetcommitment' in extractedRules and 'smartcontractpays' in extractedRules:
|
||||||
return extractedRules
|
return extractedRules
|
||||||
@ -164,37 +180,55 @@ def extractTriggerCondition(text):
|
|||||||
# Combine test
|
# Combine test
|
||||||
def parse_flodata(string):
|
def parse_flodata(string):
|
||||||
|
|
||||||
|
# todo Rule 20 - remove 'text:' from the start of flodata if it exists
|
||||||
if string[0:5] == 'text:':
|
if string[0:5] == 'text:':
|
||||||
string = string.split('text:')[1]
|
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)
|
nospacestring = re.sub(' +', ' ', string)
|
||||||
cleanstring = nospacestring.lower()
|
cleanstring = nospacestring.lower()
|
||||||
|
|
||||||
|
# todo Rule 23 - Count number of words ending with @ and #
|
||||||
atList = []
|
atList = []
|
||||||
hashList = []
|
hashList = []
|
||||||
|
|
||||||
for word in cleanstring.split(' '):
|
for word in cleanstring.split(' '):
|
||||||
if word.endswith('@'):
|
if word.endswith('@') and len(word) != 1:
|
||||||
atList.append(word)
|
atList.append(word)
|
||||||
if word.endswith('#'):
|
if word.endswith('#') and len(word) != 1:
|
||||||
hashList.append(word)
|
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 #
|
# 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:
|
if (len(atList)==0 and len(hashList)==0) or len(atList)>1 or len(hashList)>1:
|
||||||
parsed_data = {'type': 'noise'}
|
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:
|
elif len(hashList)==1 and len(atList)==0:
|
||||||
# Passing the above check means token creation or transfer
|
# Passing the above check means token creation or transfer
|
||||||
incorporation = isIncorp(cleanstring)
|
incorporation = isIncorp(cleanstring)
|
||||||
transfer = isTransfer(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)
|
initTokens = extractInitTokens(cleanstring)
|
||||||
if initTokens is not None:
|
if initTokens is not None:
|
||||||
parsed_data = {'type': 'tokenIncorporation', 'flodata': string, 'tokenIdentification': hashList[0][:-1],
|
parsed_data = {'type': 'tokenIncorporation', 'flodata': string, 'tokenIdentification': hashList[0][:-1],
|
||||||
'tokenAmount': initTokens}
|
'tokenAmount': initTokens}
|
||||||
else:
|
else:
|
||||||
parsed_data = {'type': 'noise'}
|
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:
|
elif not incorporation and transfer:
|
||||||
amount = extractAmount(cleanstring, hashList[0][:-1])
|
amount = extractAmount(cleanstring, hashList[0][:-1])
|
||||||
address = extractAddress(nospacestring)
|
address = extractAddress(nospacestring)
|
||||||
@ -204,15 +238,19 @@ def parse_flodata(string):
|
|||||||
'tokenAmount': amount, 'address': address[:-1]}
|
'tokenAmount': amount, 'address': address[:-1]}
|
||||||
else:
|
else:
|
||||||
parsed_data = {'type': 'noise'}
|
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:
|
elif len(hashList)==1 and len(atList)==1:
|
||||||
# Passing the above check means Smart Contract creation or transfer
|
# Passing the above check means Smart Contract creation or transfer
|
||||||
incorporation = isIncorp(cleanstring)
|
incorporation = isIncorp(cleanstring)
|
||||||
transfer = isTransfer(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)
|
contracttype = extractContractType(cleanstring)
|
||||||
contractaddress = extractAddress(nospacestring)
|
contractaddress = extractAddress(nospacestring)
|
||||||
contractconditions = extractContractConditions(cleanstring, 'betting*', marker)
|
contractconditions = extractContractConditions(cleanstring, 'betting*', marker)
|
||||||
@ -224,29 +262,31 @@ def parse_flodata(string):
|
|||||||
'contractConditions': contractconditions}
|
'contractConditions': contractconditions}
|
||||||
else:
|
else:
|
||||||
parsed_data = {'type': 'noise'}
|
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:
|
elif not incorporation and transfer:
|
||||||
|
# We are at the send/transfer of smart contract
|
||||||
amount = extractAmount(cleanstring, hashList[0][:-1])
|
amount = extractAmount(cleanstring, hashList[0][:-1])
|
||||||
contractcondition = extractContractCondition(cleanstring)
|
userPreference = extractContractCondition(cleanstring)
|
||||||
if None not in [amount, contractcondition]:
|
if None not in [amount, userPreference]:
|
||||||
parsed_data = {'type': 'transfer', 'transferType': 'smartContract', 'flodata': string,
|
parsed_data = {'type': 'transfer', 'transferType': 'smartContract', 'flodata': string,
|
||||||
'tokenIdentification': hashList[0][:-1],
|
'tokenIdentification': hashList[0][:-1],
|
||||||
'operation': 'transfer', 'tokenAmount': amount, 'contractName': atList[0][:-1],
|
'operation': 'transfer', 'tokenAmount': amount, 'contractName': atList[0][:-1],
|
||||||
'contractCondition': contractcondition}
|
'userPreference': userPreference}
|
||||||
else:
|
else:
|
||||||
parsed_data = {'type': 'noise'}
|
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
|
# 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)
|
triggerCondition = extractTriggerCondition(cleanstring)
|
||||||
if triggerCondition is not None:
|
if triggerCondition is not None:
|
||||||
parsed_data = {'type': 'smartContractPays', 'contractName': atList[0][:-1], 'triggerCondition': triggerCondition.group().strip()[1:-1]}
|
parsed_data = {'type': 'smartContractPays', 'contractName': atList[0][:-1], 'triggerCondition': triggerCondition.group().strip()[1:-1]}
|
||||||
else:
|
else:
|
||||||
parsed_data = {'type':'noise'}
|
parsed_data = {'type':'noise'}
|
||||||
|
else:
|
||||||
|
parsed_data = {'type': 'noise'}
|
||||||
|
|
||||||
return parsed_data
|
return parsed_data
|
||||||
|
|
||||||
result = parse_flodata("for contractname@ Smart Contract System says 'NAMO=WIN'")
|
|
||||||
print(result)
|
|
||||||
BIN
tokens/rmt.db
Normal file
BIN
tokens/rmt.db
Normal file
Binary file not shown.
@ -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)
|
|
||||||
470
tracktokens-smartcontracts.py
Normal file
470
tracktokens-smartcontracts.py
Normal 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()
|
||||||
|
|
||||||
Loading…
Reference in New Issue
Block a user