def xrp_transfer(order): """ pretty wrap the asyncio xrp transfer """ # FIXME log this event timestamp() line_number() print("\nORDER\n\n", {k: v for k, v in order.items() if k != "private"}, "\n") event = asyncio.get_event_loop().run_until_complete(xrp_transfer_execute(order)) print(it("red", "XRP TRANSFERRED")) return event
def eos_transfer(order): """ serialize, sign, and broadcast an order dictionary with nine keys """ # FIXME log this event timestamp() line_number() print("\nORDER\n\n", {k: v for k, v in order.items() if k != "private"}, "\n") nodes = eosio_nodes() while 1: nodes.append(nodes.pop(0)) node = nodes[0] # configure the url and port eosio_config.url = node eosio_config.port = "" print("\nADDRESS\n\n", node, "\n") # assemble the transfer operation dictionary operation = { "from": order["public"], "memo": "", # eos must have 4 decimal places formatted as string with space and "EOS" "quantity": precisely(order["quantity"], 4) + " EOS", "to": order["to"], } print("\nOPERATION\n\n", operation, "\n") # serialize the transfer operation raw = RawinputParams( "transfer", # the operation type operation, # the parameters "eosio.token", # the contract; for our purposes always "eosio.token" order["public"] + "@active", # the permitted party (or @owner) ) print("\nSERIALIZE\n\n", raw.params_actions_list, "\n") # sign the transfer operation params = EosioParams(raw.params_actions_list, order["private"]) print("\nSIGN\n\n", params.trx_json, "\n") # broadcast the transfer to the network try: ret = NodeNetwork.push_transaction(params.trx_json) print("\nBROADCAST\n\n", ret) if "processed" not in ret.keys(): raise ValueError("NOT PROCESSED") print(it("red", "EOS TRANSFERRED")) break except Exception as error: print(error) print(it("red", "BROADCAST FAILED"), node, "attempting new api...") continue return ret
def verify_ripple_account(account): """ ripple public api consensus of get_account() returns True or False on existance """ data = json_dumps({ "method": "account_info", "params": [{ "account": account, "strict": True, "ledger_index": "current", "queue": True, }], }) ret = get(URL, data=data).json()["result"] timestamp() line_number() print(ret, "\n") is_account = False if "account_data" in ret.keys(): is_account = True return is_account
def recycler(): """ in a background process, check incoming accounts & move funds to outbound accounts """ networks = ["eos", "xrp"] print(it("red", f"INITIALIZING RECYCLER\n"), "networks:", networks, "\n") while 1: for network in networks: order = {} # EOS specific parameters if network == "eos": nil = NIL_EOS get_balance = eos_balance transfer = eos_transfer # XRP specific parameters elif network == "xrp": nil = NIL_XRP get_balance = xrp_balance transfer = xrp_transfer # recycle gateway incoming transfers to the outbound account for idx, gate in enumerate(GATE[network]): if idx: balance = get_balance(gate["public"]) if balance > nil: timestamp() line_number() print(it("red", f"{network} RECYCLER")) print(gate["public"], balance, "\n") # finalize the order order["private"] = gate["private"] order["public"] = gate["public"] order["to"] = GATE[network][0]["public"] order["quantity"] = balance - nil # serialize, sign, and broadcast print(transfer(order), "\n") time.sleep(60)
def listener_ripple(account_idx=0, amount=None, issuer_action=None, client_id=None, nonce=0): """ for every block from initialized until detected check for transaction to the gateway issue or reserve uia upon receipt of gateway transfer :param int(account_idx) # from gateway_state.py :param float(amount) :param str(issuer_action) # None in unit test case :param str(client_id) #1.2.X :return None: """ gateway = GATE["xrp"][account_idx]["public"] uia = GATE["uia"]["xrp"]["asset_name"] start_ledger_num = get_validated_ledger() checked_ledgers = [start_ledger_num] timestamp() line_number() print(f"nonce {nonce}", "Start ledger:", start_ledger_num, "\n") start = time.time() while 1: elapsed = time.time() - start if elapsed > DEPOSIT_TIMEOUT: print(f"nonce {nonce}", it("red", "XRP GATEWAY TIMEOUT"), gateway) # after timeout, release the address if issuer_action == "issue": unlock_address("ripple", account_idx, DEPOSIT_PAUSE) break # get the latest validated ledger number current_ledger = get_validated_ledger() # get the latest ledger number we checked max_checked_ledger = max(checked_ledgers) # if there are any new validated ledgers if current_ledger > max_checked_ledger + 1: # check every ledger from last check till now for ledger_num in range(max_checked_ledger + 1, current_ledger): print( f"nonce {nonce}", it("green", "Ripple Validated Ledger"), it("yellow", ledger_num), time.ctime()[11:19], ) # get each new validated ledger transactions = get_ledger(ledger_num) # iterate through all transactions in the list of transactions for trx in transactions: if not isinstance(trx["Amount"], dict): # localize data from the transaction amount = int( trx["Amount"]) / 10**6 # convert drops to xrp trx_from = trx["Account"] trx_to = trx["Destination"] # during unit testing if issuer_action is None: print(f"nonce {nonce}", ledger_num, trx, "\n") # determine if it is a transfer to or from the gateway if gateway in [trx_from, trx_to]: timestamp() line_number() # establish gateway transfer direction direction = "INCOMING" if gateway == trx_from: direction = "OUTGOING" print( f"nonce {nonce}", it( "red", f"{direction} XRP GATEWAY TRANSFER DETECTED\n", ), f"amount {amount}\n", f"from {trx_from}\n", f"to {trx_to}\n", ) # the client_id was assigned deposit gateway address # issue UIA to client_id upon receipt of their foreign funds if issuer_action == "issue" and trx_to == gateway: print( f"nonce {nonce}", it( "red", f"ISSUING {amount} {uia} to {client_id}\n", ), ) issue("xrp", amount, client_id) # in subprocess unlock the deposit address after wait delay = DEPOSIT_TIMEOUT - elapsed + DEPOSIT_PAUSE unlock_address("xrp", account_idx, delay) return # but immediately kill the listener # the parent process will soon send foreign funds to client_id # reserve UIA upon hearing proof of this transfer if issuer_action == "reserve" and trx_from == gateway: print( f"nonce {nonce}", it("red", f"RESERVING {amount} {uia}\n"), ) reserve("xrp", amount) return # kill the listener # append this ledger number to the list of checked numbers if ledger_num not in checked_ledgers: checked_ledgers.append(ledger_num)
def on_get(self, req, resp): """ When there is a get request made to the deposit server api User GET request includes the client_id's BitShares account_name Select a gateway wallet from list currently available; remove it from the list the available address list will be stored in a json_ipc text pipe Server RESPONSE is deposit address and timeout After timeout or deposit return address to text pipe list """ confirm_time = { "eos": 30, "xrp": 2, } # create a millesecond nonce to log this event nonce = milleseconds() # extract the incoming parameters to a dictionary data = dict(req.params) timestamp() line_number() print(it("red", "DEPOSIT SERVER RECEIVED A GET REQUEST"), "\n") call(["hostname", "-I"]) print(data, "\n") client_id = data["client_id"] uia = data["uia_name"] # translate the incoming uia request to the appropriate network network = "" if uia == GATE["uia"]["xrp"]["asset_name"]: network = "xrp" elif uia == GATE["uia"]["eos"]["asset_name"]: network = "eos" print("network", network, "\n") if network in ["xrp", "eos"]: # lock an address until this transaction is complete gateway_idx = lock_address(network) print("gateway index", gateway_idx, "\n") if gateway_idx is not None: timestamp() line_number() deposit_address = GATE[network][gateway_idx]["public"] print("gateway address", deposit_address, "\n") # format a response json msg = json_dumps({ "response": "success", "server_time": nonce, "deposit_address": deposit_address, "gateway_timeout": "30 MINUTES", "msg": (f"Welcome {client_id}, please deposit your gateway issued " + f"{network} asset, to the {uia} gateway 'deposit_address' " + "in this response. Make ONE transfer to this address, " + "within the gateway_timeout specified. Transactions on " + f"this network take about {confirm_time[network]} " + "minutes to confirm."), }) print( it("red", f"STARTING {network} LISTENER TO ISSUE to {client_id}"), "\n", ) # dispatch the appropriate listener protocol listen = {"eos": listener_eosio, "xrp": listener_ripple} # in subprocess listen for payment from client_id to gateway[idx] # upon receipt issue asset, else timeout listener = Process( target=listen[network], args=(gateway_idx, None, "issue", client_id, nonce), ) listener.start() print(f"{network}listener started", "\n") else: msg = json_dumps({ "response": "error", "server_time": nonce, "msg": f"all {uia} gateway addresses are in use, " + "please try again later", }) else: msg = json_dumps({ "response": "error", "server_time": nonce, "msg": f"{uia} is an invalid gateway UIA, please try again", }) # log the response and build the response body with a data dictionary doc = str(nonce) + "_" + uia + "_" + client_id + ".txt" json_ipc(doc=doc, text=msg) time.sleep( 5) # allow some time for listener to start before offering address print(msg, "\n") resp.body = msg resp.status = HTTP_200
def listener_eosio( account_idx=0, amount=None, issuer_action=None, client_id=None, nonce=0 ): """ for every block from initialized until detected check for transaction to the gateway issue or reserve uia upon receipt of gateway transfer :param int(account_idx) # from gateway_state.py :param float(amount) :param str(issuer_action) # None in unit test case :param str(client_id) #1.2.X :return None: """ gateway = GATE["eos"][account_idx]["public"] uia = GATE["uia"]["eos"]["asset_name"] start_block_num = get_irreversible_block() checked_blocks = [start_block_num] print("Start Block:", start_block_num, "\n") # block["transactions"][0]["trx"]["transaction"]["actions"][0] holds: # ["name"] # str("transfer") etc. # ["data"] # dict.keys() [to, from, quantity] start = time.time() while 1: elapsed = time.time() - start if elapsed > DEPOSIT_TIMEOUT: print( f"nonce {nonce}", it("red", f"{nonce} EOS GATEWAY TIMEOUT"), gateway, "\n", ) # 10 minutes after timeout, release the address if issuer_action == "issue": unlock_address("eos", account_idx, DEPOSIT_PAUSE) break # get the latest irreversible block number current_block = get_irreversible_block() # get the latest block number we checked max_checked_block = max(checked_blocks) # if there are any new irreversible blocks if current_block > max_checked_block + 1: new_blocks = range(max_checked_block + 1, current_block) # eosio has a 0.5 second block time, to prevail over network latency: # *concurrently* fetch all new blocks block_processes = {} # dictionary of multiprocessing "Process" events blocks_pipe = {} # dictionary of multiprocessing "Value" pipes # spawn multpiple processes to gather the "new" blocks for block_num in new_blocks: manager = Manager() blocks_pipe[block_num] = manager.Value(c_wchar_p, "") block_processes[block_num] = Process( target=get_block, args=(block_num, blocks_pipe,) ) block_processes[block_num].start() # join all subprocess back to main process; wait for all to finish for block_num in new_blocks: block_processes[block_num].join() # extract the blocks from each "Value" in blocks_pipe blocks = {} for block_num, block in blocks_pipe.items(): # create block number keyed dict of block data dicts blocks[block_num] = block.value # with new cache of blocks, check every block from last check till now for block_num in new_blocks: print( f"nonce {nonce}", it("purple", "Eosio Irreversible Block"), it("yellow", block_num), time.ctime()[11:19], it("purple", int(time.time())), "\n", ) # get each new irreversible block block = blocks[block_num] transactions = [] try: transactions = block["transactions"] except Exception: pass # iterate through all transactions in the list of transactions for trx in transactions: actions = [] try: actions = trx["trx"]["transaction"]["actions"] except Exception: pass # if there are any, iterate through the actions for action in actions: try: # sort by tranfer ops if ( action["name"] == "transfer" # SECURITY: ensure it is the correct contract!!! and action["account"] == "eosio.token" ): # extract transfer op data qty = action["data"]["quantity"] trx_to = action["data"]["to"] trx_from = action["data"]["from"] trx_asset = qty.split(" ")[1].upper() trx_amount = float(qty.split(" ")[0]) # sort again by > nil amount of eos if trx_amount > 0.0001 and trx_asset == "EOS": # during unit testing # if issuer_action is None: if DEV: print(f"nonce {nonce}", block_num, action, "\n") # if there are any transfers listed if gateway in [trx_from, trx_to]: timestamp() line_number() print( f"nonce {nonce}", it("red", "GATEWAY TRANSFER DETECTED\n"), f"amount {trx_amount} {trx_asset}\n", f"from {trx_from}\n", f"to {trx_to}\n", ) # issue UIA to client_id # upon receipt of their foreign funds if ( issuer_action == "issue" and trx_to == gateway ): print( f"nonce {nonce}", it( "red", f"ISSUING {trx_amount} {uia} to " + f"{client_id}\n", ), ) issue("eos", trx_amount, client_id) # unlock the deposit address after some time delay = ( DEPOSIT_TIMEOUT - elapsed + DEPOSIT_PAUSE ) unlock_address("eos", account_idx, delay) return # when returning foreign funds to client, # upon receipt, reserve equal in UIA if ( issuer_action == "reserve" and trx_from == gateway and trx_amount == amount ): print( f"nonce {nonce}", it( "red", f"RESERVING {amount} {uia}\n" ), ) reserve("eos", trx_amount) return except Exception: print(f"nonce {nonce}", "action", action, "\n") print(traceback.format_exc(), "\n") if block_num not in checked_blocks: checked_blocks.append(block_num)
def withdraw(op): """ in production print_op is replaced with withdraw The user has returned some UIA to the issuer! upon hearing an on chain UIA transfer to the gateway with memo from this definition we trigger a gateway withdrawal event release the user's foreign chain funds to the memo and burn the returned UIA upon irreversible receipt """ # if its a transfer to gateway with a memo tgm = False if op[0] == 0: # transfer if op[1]["to"] in [ GATE["uia"]["eos"]["issuer_id"], GATE["uia"]["xrp"]["issuer_id"], ]: print(it("yellow", "gate uia transfer")) if "memo" in op[1].keys(): print( it("red", "TRANSFER TO GATEWAY WITH MEMO\n\n"), it("yellow", op), "\n", ) tgm = True else: print(it("red", "WARN: transfer to gateway WITHOUT memo")) if tgm: timestamp() line_number() order = {} # extract the asset_id of the transfer uia_id = op[1]["amount"]["asset_id"] print("uia_id", uia_id, "\n") # EOS specific parameters if uia_id == GATE["uia"]["eos"]["asset_id"]: network = "eos" verify = verify_eosio_account listen = listener_eosio transfer = eos_transfer # eos transfers require a url order["url"] = eosio_nodes()[ 0] # FIXME what happens if node fails? # XRP specific parameters elif uia_id == GATE["uia"]["xrp"]["asset_id"]: network = "xrp" verify = verify_ripple_account listen = listener_ripple transfer = xrp_transfer memo = op[1][ "memo"] # dict with keys("from", "to", "nonce", "message") order["private"] = GATE[network][0]["private"] order["public"] = GATE[network][0]["public"] # convert graphene operation amount to human readable order["quantity"] = (op[1]["amount"]["amount"] / 10**GATE["uia"][network]["asset_precision"]) # decode the client's memo using the issuers private key order["to"] = ovaltine(memo, GATE["uia"][network]["issuer_private"]) print(f"decoded {network} client", order["to"], "\n") # confirm we're dealing with a legit client address if verify(order["to"]): listener = Process( target=listen, args=( 0, order["quantity"], "reserve", # issuer_action None, # # always None for reserve ), ) # upon hearing real foreign chain transfer, reserve the uia equal listener.start() print( it( "red", f"STARTING {network} LISTENER TO RESERVE {order['quantity']}\n", )) # wait for listener subprocess to warm up then transfer the order time.sleep(30) timestamp() line_number() print(transfer(order)) else: print( it("red", f"WARN: memo is NOT a valid {network} account name\n"))