G1sms/silkaj/src/tx.py

280 lines
11 KiB
Python

from re import compile, search
import math
from time import sleep
from sys import exit
import urllib
from tabulate import tabulate
from network_tools import get_request, post_request, get_current_block
from tools import get_currency_symbol, get_publickey_from_seed, sign_document_from_seed,\
check_public_key, message_exit
from auth import auth_method
from wot import get_uid_from_pubkey
from money import get_last_ud_value, get_amount_from_pubkey
from constants import NO_MATCHING_ID
def send_transaction(ep, cli_args):
"""
Main function
"""
ud = get_last_ud_value(ep)
amount, output, comment, allSources, outputBackChange = cmd_transaction(cli_args, ud)
check_transaction_values(comment, output, outputBackChange)
seed = auth_method(cli_args)
issuer_pubkey = get_publickey_from_seed(seed)
tx_confirmation = transaction_confirmation(ep, cli_args, issuer_pubkey, amount, ud, output, comment)
if cli_args.contains_switches('yes') or cli_args.contains_switches('y') or \
input(tabulate(tx_confirmation, tablefmt="fancy_grid") +
"\nDo you confirm sending this transaction? [yes/no]: ") == "yes":
generate_and_send_transaction(ep, seed, issuer_pubkey, amount, output, comment, allSources, outputBackChange)
def cmd_transaction(cli_args, ud):
"""
Retrieve values from command line interface
"""
if not (cli_args.contains_definitions('amount') or cli_args.contains_definitions('amountUD')):
message_exit("--amount or --amountUD is not set")
if not cli_args.contains_definitions('output'):
message_exit("--output is not set")
if cli_args.contains_definitions('amount'):
amount = float(cli_args.get_definition('amount')) * 100
if cli_args.contains_definitions('amountUD'):
amount = float(cli_args.get_definition('amountUD')) * ud
output = cli_args.get_definition('output')
comment = cli_args.get_definition('comment') if cli_args.contains_definitions('comment') else ""
allSources = cli_args.contains_switches('allSources')
if cli_args.contains_definitions('outputBackChange'):
outputBackChange = cli_args.get_definition('outputBackChange')
else:
outputBackChange = None
return amount, output, comment, allSources, outputBackChange
def check_transaction_values(comment, output, outputBackChange):
checkComment(comment)
output = check_public_key(output, True)
if outputBackChange:
outputBackChange = check_public_key(outputBackChange, True)
if output is False or outputBackChange is False:
exit(1)
def transaction_confirmation(ep, c, issuer_pubkey, amount, ud, output, comment):
"""
Generate transaction confirmation
"""
tx = list()
currency_symbol = get_currency_symbol(get_current_block(ep)["currency"])
tx.append(["amount (" + currency_symbol + ")", amount / 100])
tx.append(["amount (UD " + currency_symbol + ")", amount / ud])
tx.append(["from", issuer_pubkey])
id_from = get_uid_from_pubkey(ep, issuer_pubkey)
if id_from is not NO_MATCHING_ID:
tx.append(["from (id)", id_from])
tx.append(["to", output])
id_to = get_uid_from_pubkey(ep, output)
if id_to is not NO_MATCHING_ID:
tx.append(["to (id)", id_to])
tx.append(["comment", comment])
return tx
def generate_and_send_transaction(ep, seed, issuers, AmountTransfered, outputAddr, Comment="", all_input=False, OutputbackChange=None):
totalamount = get_amount_from_pubkey(ep, issuers)[0]
if totalamount < AmountTransfered:
# G1SMS :: Format simple data output
#message_exit("the account: " + issuers + " don't have enough money for this transaction")
print("KO|" + str(totalamount / 100))
sys.exit(1)
while True:
listinput_and_amount = get_list_input_for_transaction(ep, issuers, AmountTransfered, all_input)
intermediatetransaction = listinput_and_amount[2]
if intermediatetransaction:
totalAmountInput = listinput_and_amount[1]
"""
print("Generate Change Transaction")
print(" - From: " + issuers)
print(" - To: " + issuers)
print(" - Amount: " + str(totalAmountInput / 100))
"""
transaction = generate_transaction_document(ep, issuers, totalAmountInput, listinput_and_amount, issuers, "Change operation")
transaction += sign_document_from_seed(transaction, seed) + "\n"
post_request(ep, "tx/process", "transaction=" + urllib.parse.quote_plus(transaction))
#print("Change Transaction successfully sent.")
print("Solde : " + format(totalamount) + " " + get_current_block(ep)["currency"] )
sleep(1) # wait 1 second before sending a new transaction
else:
"""
print("Generate Transaction:")
print(" - From: " + issuers)
print(" - To: " + outputAddr)
if all_input:
print(" - Amount: " + str(listinput_and_amount[1] / 100))
else:
print(" - Amount: " + str(AmountTransfered / 100))
"""
transaction = generate_transaction_document(ep, issuers, AmountTransfered, listinput_and_amount, outputAddr, Comment, OutputbackChange)
transaction += sign_document_from_seed(transaction, seed) + "\n"
post_request(ep, "tx/process", "transaction=" + urllib.parse.quote_plus(transaction))
#print("Transaction successfully sent.")
print( str(AmountTransfered / 100) + "|" + str( (totalamount - AmountTransfered)/100 ) )
break
def generate_transaction_document(ep, issuers, AmountTransfered, listinput_and_amount, outputaddr, Comment="", OutputbackChange=None):
check_public_key(outputaddr, True)
if OutputbackChange:
OutputbackChange = check_public_key(OutputbackChange, True)
listinput = listinput_and_amount[0]
totalAmountInput = listinput_and_amount[1]
current_blk = get_current_block(ep)
currency_name = current_blk["currency"]
blockstamp_current = str(current_blk["number"]) + "-" + str(current_blk["hash"])
curentUnitBase = current_blk["unitbase"]
if not OutputbackChange:
OutputbackChange = issuers
# if it's not a foreign exchange transaction, we remove units after 2 digits after the decimal point.
if issuers != outputaddr:
AmountTransfered = (AmountTransfered // 10 ** curentUnitBase) * 10 ** curentUnitBase
# Generate output
################
listoutput = []
# Outputs to receiver (if not himself)
rest = AmountTransfered
unitbase = curentUnitBase
while rest > 0:
outputAmount = truncBase(rest, unitbase)
rest -= outputAmount
if outputAmount > 0:
outputAmount = int(outputAmount / math.pow(10, unitbase))
listoutput.append(str(outputAmount) + ":" + str(unitbase) + ":SIG(" + outputaddr + ")")
unitbase = unitbase - 1
# Outputs to himself
unitbase = curentUnitBase
rest = totalAmountInput - AmountTransfered
while rest > 0:
outputAmount = truncBase(rest, unitbase)
rest -= outputAmount
if outputAmount > 0:
outputAmount = int(outputAmount / math.pow(10, unitbase))
listoutput.append(str(outputAmount) + ":" + str(unitbase) + ":SIG(" + OutputbackChange + ")")
unitbase = unitbase - 1
# Generate transaction document
##############################
transaction_document = "Version: 10\n"
transaction_document += "Type: Transaction\n"
transaction_document += "Currency: " + currency_name + "\n"
transaction_document += "Blockstamp: " + blockstamp_current + "\n"
transaction_document += "Locktime: 0\n"
transaction_document += "Issuers:\n"
transaction_document += issuers + "\n"
transaction_document += "Inputs:\n"
for input in listinput:
transaction_document += input + "\n"
transaction_document += "Unlocks:\n"
for i in range(0, len(listinput)):
transaction_document += str(i) + ":SIG(0)\n"
transaction_document += "Outputs:\n"
for output in listoutput:
transaction_document += output + "\n"
transaction_document += "Comment: " + Comment + "\n"
return transaction_document
def get_list_input_for_transaction(ep, pubkey, TXamount, allinput=False):
# real source in blockchain
sources = get_request(ep, "tx/sources/" + pubkey)["sources"]
if sources is None:
return None
listinput = []
for source in sources:
if source["conditions"] == "SIG(" + pubkey + ")":
listinput.append(str(source["amount"]) + ":" + str(source["base"]) + ":" + str(source["type"]) + ":" + str(source["identifier"]) + ":" + str(source["noffset"]))
# pending source
history = get_request(ep, "tx/history/" + pubkey + "/pending")["history"]
pendings = history["sending"] + history["receiving"] + history["pending"]
current_blk = get_current_block(ep)
last_block_number = int(current_blk["number"])
# add pending output
for pending in pendings:
blockstamp = pending["blockstamp"]
block_number = int(blockstamp.split("-")[0])
# if it's not an old transaction (bug in mirror node)
if block_number >= last_block_number - 3:
identifier = pending["hash"]
i = 0
for output in pending["outputs"]:
outputsplited = output.split(":")
if outputsplited[2] == "SIG("+pubkey+")":
inputgenerated = str(outputsplited[0]) + ":" + str(outputsplited[1]) + ":T:" + identifier + ":" + str(i)
if inputgenerated not in listinput:
listinput.append(inputgenerated)
i += 1
# remove input already used
for pending in pendings:
blockstamp = pending["blockstamp"]
block_number = int(blockstamp.split("-")[0])
# if it's not an old transaction (bug in mirror node)
if block_number >= last_block_number - 3:
for input in pending["inputs"]:
if input in listinput:
listinput.remove(input)
# generate final list source
listinputfinal = []
totalAmountInput = 0
intermediatetransaction = False
for input in listinput:
listinputfinal.append(input)
inputsplit = input.split(":")
totalAmountInput += int(inputsplit[0]) * 10 ** int(inputsplit[1])
TXamount -= int(inputsplit[0]) * 10 ** int(inputsplit[1])
# if more 40 sources, it's an intermediate transaction
if len(listinputfinal) >= 40:
intermediatetransaction = True
break
if TXamount <= 0 and not allinput:
break
if TXamount > 0 and not intermediatetransaction:
message_exit("Error: you don't have enough money")
return listinputfinal, totalAmountInput, intermediatetransaction
def checkComment(Comment):
if len(Comment) > 255:
message_exit("Error: Comment is too long")
regex = compile('^[0-9a-zA-Z\ \-\_\:\/\;\*\[\]\(\)\?\!\^\+\=\@\&\~\#\{\}\|\\\<\>\%\.]*$')
if not search(regex, Comment):
message_exit("Error: the format of the comment is invalid")
def truncBase(amount, base):
pow = math.pow(10, base)
if amount < pow:
return 0
return math.trunc(amount / pow) * pow