def unit_test_gateway_state(): """ initialize the state machine with list of 1's claim two gateway addresses for deposit launch two supbrocesses to release the deposit addresses; on now another later check state, wait, check state again """ print("\033c") print("\n\nunit test gateway deposit address state machine\n\n") initialize_addresses("xrp") print(json_ipc(f"xrp_gateway_state.txt")) print("\n\nlocking an xrp address\n") address_idx = lock_address("xrp") print("address index", address_idx) print(json_ipc(f"xrp_gateway_state.txt")) print("\n\nlocking another xrp address\n") address_idx = lock_address("xrp") print("address index", address_idx) print(json_ipc(f"xrp_gateway_state.txt")) print( "\n\nlaunching subprocess unlocking xrp address 0 immediately\n\nAND") print( "\nlaunching second subprocess unlocking xrp address 1 after 10 seconds\n" ) time.sleep(0.1) unlock_address("xrp", 0, 0) time.sleep(0.1) unlock_address("xrp", 1, 10) print(json_ipc(f"xrp_gateway_state.txt")) print("\n\nprimary process waiting 10 seconds\n") time.sleep(11) print(json_ipc(f"xrp_gateway_state.txt"))
def block_maven(maven_id, start, stop): """ BitShares public api consensus of get_block() returns all tx's on a given block :param int(maven_id): used for inter process communication channel identification :param int(start): beginning block number to get block transaction data :param int(stop): latest irreversible block number :return None: reports via text file inter process communication """ rpc = wss_handshake("") # dictionary that will contain a list of transactions on each block blocks = {} for block_num in range(start, stop): blocks[block_num] = [] json_ipc(doc=f"block_maven_{maven_id}.txt", text=json_dumps(blocks)) for block_num in range(start, stop): while True: try: ret = wss_query( rpc, ["database", "get_block", [block_num]])["transactions"] if isinstance(ret, list): blocks[block_num] = ret break rpc = wss_handshake(rpc) except Exception: rpc = wss_handshake(rpc) continue json_ipc(doc=f"block_maven_{maven_id}.txt", text=json_dumps(blocks))
def block_num_maven(maven_id): """ BTS public api maven opinion of last irreversible block number :param int(maven_id) used for inter process communication channel identification """ rpc = "" rpc = wss_handshake(rpc) while True: if not randint(0, 100): rpc = wss_handshake(rpc) try: ret = wss_query(rpc, ["database", "get_dynamic_global_properties", []]) block_time = from_iso_date(ret["time"]) if time.time() - block_time > 10: continue block_num = int(ret["last_irreversible_block_num"]) latest = json_ipc(doc="block_number.txt")[0] if latest + 1000 < block_num < latest - 10: continue json_ipc(doc=f"block_num_maven_{maven_id}.txt", text=json_dumps([ block_num, ])) except Exception: rpc = wss_handshake(rpc) time.sleep(3)
def unlock_address_process(chain, idx, delay): """ check the binary state of the gateway addresses reset the freed address state to 1 """ time.sleep(delay) doc = f"{chain}_gateway_state.txt" gateway_state = json_ipc(doc=doc) gateway_state[idx] = 1 json_ipc(doc=doc, text=json_dumps(gateway_state))
def initialize_addresses(chain): """ create the IPC file with a list of ones ie [1,1,1,...] the addresses are "all available" on startup. """ initial_state = [] doc = f"{chain}_gateway_state.txt" for _ in GATE[chain]: initial_state.append(1) json_ipc(doc=doc, text=json_dumps(initial_state))
def create_database(): """ initialize an empty text pipe IPC json_ipc :return None: """ os.makedirs(PATH + "pipe", exist_ok=True) for maven_id in range(BLOCK_NUM_MAVENS): json_ipc(doc=f"block_num_maven_{maven_id}.txt", text=json_dumps([ 0, ])) json_ipc(doc=f"block_number.txt", text=json_dumps([ 0, ]))
def lock_address(chain): """ check the binary state of the gateway addresses if an address is available, switch its state to zero and return its index else return None """ doc = f"{chain}_gateway_state.txt" gateway_idx = None gateway_state = json_ipc(doc=doc) for idx, state in enumerate(gateway_state): if state: gateway_idx = idx gateway_state[idx] = 0 break json_ipc(doc=doc, text=json_dumps(gateway_state)) return gateway_idx
def main(): """ setting state of all inbound accounts to available subprocess auto send all inbound funds to outbound account subprocess deposit api server subprocess bitshares withdrawal listener """ print("\033c\n") print(it("yellow", logo())) # initialize the the pipe folder of *txt files json_ipc(initialize=True) # set state machine to "all incoming accounts available" initialize_addresses("xrp") initialize_addresses("eos") # spawn 3 concurrent gateway subprocesses recycling() time.sleep(0.2) deposit_api_server() time.sleep(0.2) withdraw_listener_bitshares()
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_bitshares(selection=None): """ primary listener event loop :param int(selection) or None: user choice for demonstration of listener :run forever: """ # get node list from github repo for bitshares ui staging; write to file nodes = bitshares_nodes() options = raw_operations() json_ipc(doc="nodes.txt", text=json_dumps(nodes)) # create a subfolder for the database; write to file create_database() # initialize block number last_block_num = curr_block_num = 0 # bypass user input... gateway transfer ops act = print_op if selection is None: selection = 0 act = withdraw # spawn subprocesses for gathering streaming consensus irreversible block number spawn_block_num_processes() # continually listen for last block["transaction"]["operations"] print(it("red", "\nINITIALIZING WITHDRAWAL LISTENER\n\n")) while True: try: # get the irreversible block number reported by each maven subprocess block_numbers = [] for maven_id in range(BLOCK_NUM_MAVENS): block_num = json_ipc(doc=f"block_num_maven_{maven_id}.txt")[0] block_numbers.append(block_num) # the current block number is the statistical mode of the mavens # NOTE: may throw StatisticsError when no mode curr_block_num = mode(block_numbers) # print(curr_block_num) json_ipc(doc=f"block_number.txt", text=json_dumps([ curr_block_num, ])) # if the irreverisble block number has advanced if curr_block_num > last_block_num: print( "\033[F", # go back one line it("blue", "BitShares Irreversible Block"), it("yellow", curr_block_num), time.ctime()[11:19], it("blue", int(time.time())), ) if last_block_num > 0: # not on first iter # spawn some new mavens to get prospective block data start = last_block_num + 1 stop = curr_block_num + 1 spawn_block_processes(start, stop) # inititialize blocks as a dict of empty transaction lists blocks = {} for block_num in range(start, stop): blocks[block_num] = [] # get block transactions from each maven subprocesses for maven_id in range(BLOCK_MAVENS): # print(maven_id) maven_blocks = json_ipc( doc=f"block_maven_{maven_id}.txt") # for each block that has past since last update for block_num in range(start, stop): # print(block_num) # get the maven's version of that block from the dictionary # NOTE: may throw KeyError, TODO: find out why? maven_block = maven_blocks[str(block_num)] # append that version to the list # of maven opinions for that block number blocks[block_num].append(json_dumps(maven_block)) # get the mode of the mavens for each block in the blocks dict # NOTE: may throw StatisticsError when no mode # for example half the nodes are on the next block number blocks = { k: json_loads(mode(v)) for k, v in blocks.items() } # triple nested: # for each operation, in each transaction, on each block for block_num, transactions in blocks.items(): for item, trx in enumerate(transactions): for op in trx["operations"]: # add the block and transaction numbers to the operation op[1]["block"] = block_num op[1]["trx"] = item + 1 op[1]["operation"] = (op[0], options[op[0]]) # spin off withdrawal act so listener can continue process = Process(target=act, args=(op, )) process.start() last_block_num = curr_block_num time.sleep(6) # statistics and key errors can be safely ignored, restart loop except (StatisticsError, KeyError): continue # in all other cases provide stack trace except Exception as error: print("\n\n", it("yellow", error), "\n\n") print(traceback.format_exc(), "\n") continue