import binascii import cmd import json import os import pandas as pd from pathlib import Path import pprint import time from typing import Callable, Dict, List, Optional, Union from app import App, balances_dataframe from command import AccountTx, Subscribe from common import Account, Asset, XRP from transaction import SetHook, Payment, Trust def clear_screen(): if os.name == 'nt': _ = os.system('cls') else: _ = os.system('clear') # Directory to find hooks. The hook should be in a directory call "hook_name" # in a file called "hook_name.wasm" HOOKS_DIR = Path() def set_hooks_dir(n: str): global HOOKS_DIR if n: HOOKS_DIR = Path(n) _valid_hook_names = ['doubler', 'notascam'] def _file_to_hex(filename: Path) -> str: with open(filename, 'rb') as f: content = f.read() return binascii.hexlify(content).decode('utf8') def _removesuffix(self: str, suffix: str) -> str: if suffix and self.endswith(suffix): return self[:-len(suffix)] else: return self[:] class SidechainRepl(cmd.Cmd): ''' Simple repl for interacting with side chains ''' intro = '\n\nWelcome to the sidechain test shell. Type help or ? to list commands.\n' prompt = 'RiplRepl> ' def preloop(self): clear_screen() def __init__(self, mc_app: App, sc_app: App): super().__init__() assert mc_app.is_alias('door') and sc_app.is_alias('door') self.mc_app = mc_app self.sc_app = sc_app def _complete_chain(self, text, line): if not text: return ['mainchain', 'sidechain'] else: return [ c for c in ['mainchain', 'sidechain'] if c.startswith(text) ] def _complete_unit(self, text, line): if not text: return ['drops', 'xrp'] else: return [c for c in ['drops', 'xrp'] if c.startswith(text)] def _complete_account(self, text, line, chain_name=None): known_accounts = set() chains = [self.mc_app, self.sc_app] if chain_name == 'mainchain': chains = [self.mc_app] elif chain_name == 'sidechain': chains = [self.sc_app] for chain in chains: known_accounts = known_accounts | set( [a.nickname for a in chain.known_accounts()]) if not text: return list(known_accounts) else: return [c for c in known_accounts if c.startswith(text)] def _complete_asset(self, text, line, chain_name=None): known_assets = set() chains = [self.mc_app, self.sc_app] if chain_name == 'mainchain': chains = [self.mc_app] elif chain_name == 'sidechain': chains = [self.sc_app] for chain in chains: known_assets = known_assets | set(chain.known_asset_aliases()) if not text: return list(known_assets) else: return [c for c in known_assets if c.startswith(text)] ################## # addressbook def do_addressbook(self, line): def print_addressbook(chain: App, chain_name: str, nickname: Optional[str]): if nickname and not chain.is_alias(nickname): print( f"{nickname} is not part of {chain_name}'s address book.") print(f'{chain_name}:\n{chain.key_manager.to_string(nickname)}') args = line.split() if len(args) > 2: print( f'Error: Too many arguments to addressbook command. Type "help" for help.' ) return chains = [self.mc_app, self.sc_app] chain_names = ['mainchain', 'sidechain'] nickname = None if args and args[0] in ['mainchain', 'sidechain']: chain_names = [args[0]] if args[0] == 'mainchain': chains = [self.mc_app] else: chains = [self.sc_app] args.pop(0) if args: nickname = args[0] for chain, name in zip(chains, chain_names): print_addressbook(chain, name, nickname) print('\n') def complete_addressbook(self, text, line, begidx, endidx): args = line.split() arg_num = len(args) if arg_num == 2: # chain return self._complete_chain(text, line) + self._complete_account( text, line) if arg_num == 3: # account return self._complete_account(text, line, chain_name=args[1]) return [] def help_addressbook(self): print('\n'.join([ 'addressbook [mainchain | sidechain] [account]', 'Show the address book for the specified chain and account.', 'If a chain is not specified, show both address books.', 'If the account is not specified, show all addresses.', '' ])) # addressbook ################## ################## # balance def do_balance(self, line): args = line.split() if len(args) > 3: print( f'Error: Too many arguments to balance command. Type "help" for help.' ) return in_drops = False if args and args[-1] in ['xrp', 'drops']: unit = args[-1] args.pop() if unit == 'xrp': in_drops = False elif unit == 'drops': in_drops = True chains = [self.mc_app, self.sc_app] chain_names = ['mainchain', 'sidechain'] if args and args[0] in ['mainchain', 'sidechain']: chain_names = [args[0]] args.pop(0) if chain_names[0] == 'mainchain': chains = [self.mc_app] else: chains = [self.sc_app] account_ids = [None] * len(chains) if args: nickname = args[0] args.pop() account_ids = [] for c in chains: if not c.is_alias(nickname): print(f'Error: {nickname} is not in the address book') return account_ids.append(c.account_from_alias(nickname)) assets = [[XRP(0)]] * len(chains) if args: asset_alias = args[0] args.pop() if len(chains) != 1: print( f'Error: iou assets can only be shown for a single chain at a time' ) return if not chains[0].is_asset_alias(asset_alias): print(f'Error: {asset_alias} is not a valid asset alias') return assets = [[chains[0].asset_from_alias(asset_alias)]] else: # XRP and all assets in the assets alias list assets = [[XRP(0)] + c.known_iou_assets() for c in chains] assert not args df = balances_dataframe(chains, chain_names, account_ids, assets, in_drops) df_as_str = df.to_string(float_format=lambda x: f'{x:,.6f}') print(f'{df_as_str}\n') def complete_balance(self, text, line, begidx, endidx): args = line.split() arg_num = len(args) if arg_num == 2: # chain or account return self._complete_chain(text, line) + self._complete_account( text, line) elif arg_num == 3: # account or unit or asset_alias return self._complete_account(text, line) + self._complete_unit( text, line, chain_name=args[1]) + self._complete_asset( text, line, chain_name=args[1]) elif arg_num == 4: # unit return self._complete_unit(text, line) + self._complete_asset( text, line, chain_name=args[1]) return [] def help_balance(self): print('\n'.join([ f'balance [sidechain | mainchain] [account_name] [xrp | drops | asset_alias]', 'Show the balance the specified account.' 'If no account is specified, show the balance for all accounts in the addressbook.', 'If no chain is specified, show the balances for both chains.', '' 'If no asset alias is specified, show balances for all known asset aliases.' ])) # balance ################## ################## # account_info def _account_info_df(self, chain: App, acc: Optional[Account]): b = chain.get_account_info(acc) b = chain.substitute_nicknames(b) b = b.set_index('account') return b def do_account_info(self, line): args = line.split() if len(args) > 2: print( f'Error: Too many arguments to account_info command. Type "help" for help.' ) return chains = [self.mc_app, self.sc_app] chain_names = ['mainchain', 'sidechain'] if args and args[0] in ['mainchain', 'sidechain']: chain_names = [args[0]] args.pop(0) if chain_names[0] == 'mainchain': chains = [self.mc_app] else: chains = [self.sc_app] account_ids = [None] * len(chains) if args: nickname = args[0] args.pop() account_ids = [] for c in chains: if not c.is_alias(nickname): print(f'Error: {nickname} is not in the address book') return account_ids.append(c.account_from_alias(nickname)) assert not args dfs = [] keys = [] for chain, chain_name, acc in zip(chains, chain_names, account_ids): dfs.append(self._account_info_df(chain, acc)) keys.append(_removesuffix(chain_name, 'chain')) df = pd.concat(dfs, keys=keys) df_as_str = df.to_string(float_format=lambda x: f'{x:,.6f}') print(f'{df_as_str}\n') def complete_account_info(self, text, line, begidx, endidx): args = line.split() arg_num = len(args) if arg_num == 2: # chain or account return self._complete_chain(text, line) + self._complete_account( text, line) elif arg_num == 3: # account return self._complete_account(text, line) return [] def help_account_info(self): print('\n'.join([ f'account_info [sidechain | mainchain] [account_name]', 'Show the account_info the specified account.' 'If no account is specified, show the account_info for all accounts in the addressbook.', 'If no chain is specified, show the account_info for both chains.', ])) # account_info ################## ################## # pay def do_pay(self, line): args = line.split() if len(args) < 4: print( f'Error: Too few arguments to pay command. Type "help" for help.' ) return if len(args) > 5: print( f'Error: Too many arguments to pay command. Type "help" for help.' ) return in_drops = False if args and args[-1] in ['xrp', 'drops']: unit = args[-1] if unit == 'xrp': in_drops = False elif unit == 'drops': in_drops = True args.pop() chain = None if args[0] not in ['mainchain', 'sidechain']: print( f'Error: First argument must specify the chain. Type "help" for help.' ) return if args[0] == 'mainchain': chain = self.mc_app else: chain = self.sc_app args.pop(0) nickname = args[0] if nickname == 'door': print( f'Error: The "door" account should never be used as a source of payments.' ) return if not chain.is_alias(nickname): print(f'Error: {nickname} is not in the address book') return src_account = chain.account_from_alias(nickname) args.pop(0) nickname = args[0] if nickname == 'door': print( f'Error: "pay" cannot be used for cross chain transactions. Use the "xchain" command instead.' ) return if not chain.is_alias(nickname): print(f'Error: {nickname} is not in the address book') return dst_account = chain.account_from_alias(nickname) args.pop(0) amt_value = None try: amt_value = int(args[0]) except: try: if not in_drops: amt_value = float(args[0]) except: pass if amt_value is None: print(f'Error: {args[0]} is an invalid amount.') return args.pop(0) asset = Asset(value=0) if args: asset_alias = args[0] args.pop(0) if not chain.is_asset_alias(asset_alias): print(f'Error: {args[0]} is an invalid asset alias.') return asset = chain.asset_from_alias(asset_alias) assert not args if asset.is_xrp() and not in_drops: amt_value *= 1_000_000 amt = asset(value=amt_value) chain(Payment(account=src_account, dst=dst_account, amt=amt)) chain.maybe_ledger_accept() def complete_pay(self, text, line, begidx, endidx): args = line.split() arg_num = len(args) if not text: arg_num += 1 if arg_num == 2: # chain return self._complete_chain(text, line) elif arg_num == 3: # account return self._complete_account(text, line, chain_name=args[1]) elif arg_num == 4: # account return self._complete_account(text, line, chain_name=args[1]) elif arg_num == 5: # amount completions = [] elif arg_num == 6: # drops or xrp or asset return self._complete_unit(text, line) + self._complete_asset( text, line, chain_name=args[1]) return [] def help_pay(self): print('\n'.join([ f'pay (sidechain | mainchain) src_account dst_account amount [xrp | drops | iou_alias]', 'Send xrp from the src account to the dst account.' 'Note: the door account can not be used as the src or dst.', 'Cross chain transactions should use the xchain command instead of this.', '' ])) # pay ################## ################## # xchain def do_xchain(self, line): args = line.split() if len(args) < 4: print( f'Error: Too few arguments to pay command. Type "help" for help.' ) return if len(args) > 5: print( f'Error: Too many arguments to pay command. Type "help" for help.' ) return in_drops = False if args and args[-1] in ['xrp', 'drops']: unit = args[-1] if unit == 'xrp': in_drops = False elif unit == 'drops': in_drops = True args.pop() chain = None if args[0] not in ['mainchain', 'sidechain']: print( f'Error: First argument must specify the chain. Type "help" for help.' ) return if args[0] == 'mainchain': chain = self.mc_app other_chain = self.sc_app else: chain = self.sc_app other_chain = self.mc_app args.pop(0) nickname = args[0] if nickname == 'door': print( f'Error: The "door" account can not be used as the source of cross chain funds.' ) return if not chain.is_alias(nickname): print(f'Error: {nickname} is not in the address book') return src_account = chain.account_from_alias(nickname) args.pop(0) nickname = args[0] if nickname == 'door': print( f'Error: The "door" account can not be used as the destination of cross chain funds.' ) return if not other_chain.is_alias(nickname): print(f'Error: {nickname} is not in the address book') return dst_account = other_chain.account_from_alias(nickname) args.pop(0) amt_value = None try: amt_value = int(args[0]) except: try: if not in_drops: amt_value = float(args[0]) except: pass if amt_value is None: print(f'Error: {args[0]} is an invalid amount.') return args.pop(0) asset = Asset(value=0) if args: asset_alias = args[0] args.pop(0) if not chain.is_asset_alias(asset_alias): print(f'Error: {asset_alias} is an invalid asset alias.') return asset = chain.asset_from_alias(asset_alias) assert not args if asset.is_xrp() and not in_drops: amt_value *= 1_000_000 amt = asset(value=amt_value) assert not args memos = [{'Memo': {'MemoData': dst_account.account_id_str_as_hex()}}] door_account = chain.account_from_alias('door') chain( Payment(account=src_account, dst=door_account, amt=amt, memos=memos)) chain.maybe_ledger_accept() if other_chain.standalone: # from_chain (side chain) sends a txn, but won't close the to_chain (main chain) ledger time.sleep(2) other_chain.maybe_ledger_accept() def complete_xchain(self, text, line, begidx, endidx): args = line.split() arg_num = len(args) if not text: arg_num += 1 if arg_num == 2: # chain return self._complete_chain(text, line) elif arg_num == 3: # this chain account return self._complete_account(text, line, chain_name=args[1]) elif arg_num == 4: # other chain account other_chain_name = None if args[1] == 'mainchain': other_chain_name = 'sidechain' if args[1] == 'sidechain': other_chain_name = 'mainchain' return self._complete_account(text, line, chain_name=other_chain_name) elif arg_num == 5: # amount completions = [] elif arg_num == 6: # drops or xrp or asset return self._complete_unit(text, line) + self._complete_asset( test, line, chain_name=args[1]) return [] def help_xchain(self): print('\n'.join([ f'xchain (sidechain | mainchain) this_chain_account other_chain_account amount [xrp | drops | iou_alias]', 'Send xrp from the specified chain to the other chain.' 'Note: the door account can not be used as the account.', '' ])) # xchain ################## ################## # server_info def do_server_info(self, line): def data_dict(chain: App, chain_name: str): file_names = [c.get_file_name() for c in chain.get_configs()] data = { 'pid': chain.get_pids(), 'config': file_names, 'running': chain.get_running_status() } bsi = chain.get_brief_server_info() data.update(bsi) df = pd.DataFrame(data=data) indexes = [[], []] for i in range(len(file_names)): indexes[0].append(chain_name) indexes[1].append(i) data['indexes'] = indexes return data def df_from_dicts(d1: dict, d2: Optional[dict] = None) -> pd.DataFrame: indexes = [[], []] for i in range(2): if d2: indexes[i] = d1['indexes'][i] + d2['indexes'][i] else: indexes[i] = d1['indexes'][i] data = {} for k in d1.keys(): if k == 'indexes': continue if d2: data[k] = d1[k] + d2[k] else: data[k] = d1[k] if k == 'config': # save space by omitting the common prefix on the configs cp = os.path.commonprefix(data[k]) data[k] = [os.path.relpath(f, cp) for f in data[k]] return pd.DataFrame(data=data, index=indexes) args = line.split() if len(args) > 1: print( f'Error: Too many arguments to server_info command. Type "help" for help.' ) return chains = [self.mc_app, self.sc_app] chain_names = ['mainchain', 'sidechain'] if args and args[0] in ['mainchain', 'sidechain']: chain_names = [args[0]] if args[0] == 'mainchain': chains = [self.mc_app] else: chains = [self.sc_app] args.pop(0) data_dicts = [ data_dict(chain, _removesuffix(name, 'chain')) for chain, name in zip(chains, chain_names) ] df = df_from_dicts(*data_dicts) print(f'{df.to_string(index=True)}') def complete_server_info(self, text, line, begidx, endidx): arg_num = len(line.split()) if arg_num == 2: # chain return self._complete_chain(text, line) return [] def help_server_info(self): print('\n'.join([ 'server_info [mainchain | sidechain]', 'Show the process ids and config files for the rippled servers running for the specified chain.', 'If a chain is not specified, show info for both chains.', ])) # server_info ################## ################## # federator_info def do_federator_info(self, line): args = line.split() indexes = set() verbose = False raw = False while args and (args[-1] == 'verbose' or args[-1] == 'raw'): if args[-1] == 'verbose': verbose = True if args[-1] == 'raw': raw = True args.pop() try: for i in args: indexes.add(int(i)) except: f'Error: federator_info bad arguments: {args}. Type "help" for help.' def global_df(info_dict: dict) -> pd.DataFrame: indexes = [] keys = [] mc_last_sent_seq = [] mc_seq = [] mc_num_pending = [] mc_sync_state = [] sc_last_sent_seq = [] sc_seq = [] sc_num_pending = [] sc_sync_state = [] for (k, v) in info_dict.items(): indexes.append(k) info = v['info'] keys.append(info['public_key']) mc = info['mainchain'] sc = info['sidechain'] mc_last_sent_seq.append(mc['last_transaction_sent_seq']) sc_last_sent_seq.append(sc['last_transaction_sent_seq']) mc_seq.append(mc['sequence']) sc_seq.append(sc['sequence']) mc_num_pending.append(len(mc['pending_transactions'])) sc_num_pending.append(len(sc['pending_transactions'])) if 'state' in mc['listener_info']: mc_sync_state.append(mc['listener_info']['state']) else: mc_sync_state.append(None) if 'state' in sc['listener_info']: sc_sync_state.append(sc['listener_info']['state']) else: sc_sync_state.append(None) data = { ('key', ''): keys, ('mainchain', 'last_sent_seq'): mc_last_sent_seq, ('mainchain', 'seq'): mc_seq, ('mainchain', 'num_pending'): mc_num_pending, ('mainchain', 'sync_state'): mc_sync_state, ('sidechain', 'last_sent_seq'): sc_last_sent_seq, ('sidechain', 'seq'): sc_seq, ('sidechain', 'num_pending'): sc_num_pending, ('sidechain', 'sync_state'): sc_sync_state } return pd.DataFrame(data=data, index=indexes) def pending_df(info_dict: dict, verbose=False) -> pd.DataFrame: indexes = [[], []] amounts = [] dsts = [] num_sigs = [] hashes = [] signatures = [] for (k, v) in info_dict.items(): for chain in ['mainchain', 'sidechain']: info = v['info'][chain] pending = info['pending_transactions'] idx = (k, chain) for t in pending: amt = t['amount'] try: amt = int(amt) / 1_000_000.0 except: pass dst = t['destination_account'] h = t['hash'] ns = len(t['signatures']) if not verbose: indexes[0].append(idx[0]) indexes[1].append(idx[1]) amounts.append(amt) dsts.append(dst) hashes.append(h) num_sigs.append(ns) else: for sig in t['signatures']: indexes[0].append(idx[0]) indexes[1].append(idx[1]) amounts.append(amt) dsts.append(dst) hashes.append(h) num_sigs.append(ns) signatures.append(sig['public_key']) data = { 'amount': amounts, 'dest_account': dsts, 'num_sigs': num_sigs, 'hash': hashes } if verbose: data['sigs'] = signatures return pd.DataFrame(data=data, index=indexes) info_dict = self.sc_app.federator_info(indexes) if raw: pprint.pprint(info_dict) return gdf = global_df(info_dict) print(gdf) # pending print() pdf = pending_df(info_dict, verbose) print(pdf) def complete_federator_info(self, text, line, begidx, endidx): args = line.split() if 'verbose'.startswith(args[-1]): return ['verbose'] if 'raw'.startswith(args[-1]): return ['raw'] running_status = sc_app.get_running_status() return [ str(i) for i in range(0, len(sc_app.get_running_status())) if running_status[i] ] def help_federator_info(self): print('\n'.join([ 'federator_info [server_index...] [verbose | raw]', 'Show the state of the federators queues and startup synchronization.', 'If a server index is not specified, show info for all running federators.', ])) # federator_info ################## ################## # new_account def do_new_account(self, line): args = line.split() if len(args) < 2: print( f'Error: new_account command takes at least two arguments. Type "help" for help.' ) return chain = None if args[0] not in ['mainchain', 'sidechain']: print( f'Error: The first argument must be "mainchain" or "sidechain".' ) return if args[0] == 'mainchain': chain = self.mc_app else: chain = self.sc_app args.pop(0) for alias in args: if chain.is_alias(alias): print(f'Warning: The alias {alias} already exists.') else: chain.create_account(alias) def complete_new_account(self, text, line, begidx, endidx): arg_num = len(line.split()) if arg_num == 2: # chain return self._complete_chain(text, line) return [] def help_new_account(self): print('\n'.join([ 'new_account (mainchain | sidechain) alias [alias...]', 'Add a new account to the address book', ])) # new_account ################## ################## # new_iou def do_new_iou(self, line): args = line.split() if len(args) != 4: print( f'Error: new_iou command takes exactly four arguments. Type "help" for help.' ) return chain = None if args[0] not in ['mainchain', 'sidechain']: print( f'Error: The first argument must be "mainchain" or "sidechain".' ) return if args[0] == 'mainchain': chain = self.mc_app else: chain = self.sc_app args.pop(0) (alias, currency, issuer) = args if chain.is_asset_alias(alias): print(f'Error: The alias {alias} already exists.') return if not chain.is_alias(issuer): print( f'Error: The issuer {issuer} is not part of the address book.') return asset = Asset(value=0, currency=currency, issuer=chain.account_from_alias(issuer)) chain.add_asset_alias(asset, alias) def complete_new_iou(self, text, line, begidx, endidx): arg_num = len(line.split()) if arg_num == 2: # chain return self._complete_chain(text, line) if arg_num == 5: # issuer return self._complete_account(text, line) return [] def help_new_iou(self): print('\n'.join([ 'new_iou (mainchain | sidechain) alias currency issuer', 'Add a new iou alias', ])) # new_iou ################## ################## # ious def do_ious(self, line): def print_ious(chain: App, chain_name: str, nickname: Optional[str]): if nickname and not chain.is_asset_alias(nickname): print( f"{nickname} is not part of {chain_name}'s asset aliases.") print(f'{chain_name}:\n{chain.asset_aliases.to_string(nickname)}') args = line.split() if len(args) > 2: print( f'Error: Too many arguments to ious command. Type "help" for help.' ) return chains = [self.mc_app, self.sc_app] chain_names = ['mainchain', 'sidechain'] nickname = None if args and args[0] in ['mainchain', 'sidechain']: chain_names = [args[0]] if args[0] == 'mainchain': chains = [self.mc_app] else: chains = [self.sc_app] args.pop(0) if args: nickname = args[0] for chain, name in zip(chains, chain_names): print_ious(chain, name, nickname) print('\n') def complete_ious(self, text, line, begidx, endidx): args = line.split() arg_num = len(args) if arg_num == 2: # chain or iou return self._complete_chain(text, line) + self._complete_asset( text, line) if arg_num == 3: # iou return self._complete_asset(text, line, chain_name=args[1]) return [] def help_ious(self): print('\n'.join([ 'ious [mainchain | sidechain] [alias]', 'Show the iou aliases for the specified chain and alias.', 'If a chain is not specified, show aliases for both chains.', 'If the alias is not specified, show all aliases.', '' ])) # ious ################## ################## # set_trust def do_set_trust(self, line): args = line.split() if len(args) != 4: print( f'Error: set_trust command takes exactly four arguments. Type "help" for help.' ) return chain = None if args[0] not in ['mainchain', 'sidechain']: print( f'Error: The first argument must be "mainchain" or "sidechain".' ) return if args[0] == 'mainchain': chain = self.mc_app else: chain = self.sc_app args.pop(0) (alias, accountStr, amountStr) = args if not chain.is_asset_alias(alias): print(f'Error: The alias {alias} does not exists.') return if not chain.is_alias(accountStr): print( f'Error: The issuer {issuer} is not part of the address book.') return account = chain.account_from_alias(accountStr) amount = None try: amount = int(amountStr) except: try: amount = float(amountStr) except: pass if amount is None: print(f'Error: Invalid amount {amountStr}') return asset = chain.asset_from_alias(alias)(amount) chain(Trust(account=account, limit_amt=asset)) chain.maybe_ledger_accept() def complete_set_trust(self, text, line, begidx, endidx): args = line.split() arg_num = len(args) if arg_num == 2: # chain return self._complete_chain(text, line) if arg_num == 3: # iou return self._complete_asset(text, line, chain_name=args[1]) if arg_num == 4: # account return self._complete_account(text, line, chain_name=args[1]) return [] def help_set_trust(self): print('\n'.join([ 'set_trust (mainchain | sidechain) iou_alias account amount', "Set trust amount for account's side of the iou trust line to amount", ])) # set_trust ################## ################## # ledger_accept def do_ledger_accept(self, line): args = line.split() if len(args) != 1: print( f'Error: ledger_accept command takes exactly one argument. Type "help" for help.' ) return chain = None if args[0] not in ['mainchain', 'sidechain']: print( f'Error: The first argument must be "mainchain" or "sidechain".' ) return if args[0] == 'mainchain': chain = self.mc_app else: chain = self.sc_app args.pop(0) assert not args chain.maybe_ledger_accept() def complete_ledger_accept(self, text, line, begidx, endidx): arg_num = len(line.split()) if arg_num == 2: # chain return self._complete_chain(text, line) return [] def help_ledger_accept(self): print('\n'.join([ 'ledger_accept (mainchain | sidechain)', 'Force a ledger_accept if the chain is running in stand alone mode.', ])) # ledger_accept ################## ################## # server_start def do_server_start(self, line): args = line.split() if len(args) == 0: print( f'Error: server_start command takes one or more arguments. Type "help" for help.' ) return indexes = set() if len(args) == 1 and args[0] == 'all': # re-start all stopped servers running_status = self.sc_app.get_running_status() for (i, running) in enumerate(running_status): if not running: indexes.add(i) else: try: for i in args: indexes.add(int(i)) except: f'Error: server_start bad arguments: {args}. Type "help" for help.' self.sc_app.servers_start(indexes) def complete_server_start(self, text, line, begidx, endidx): running_status = sc_app.get_running_status() if 'all'.startswith(text): return ['all'] return [ str(i) for (i, running) in enumerate(running_status) if not running and str(i).startswith(text) ] def help_server_start(self): print('\n'.join([ 'server_start index [index...] | all', 'Start a running server', ])) # server_start ################## ################## # server_stop def do_server_stop(self, line): args = line.split() if len(args) == 0: print( f'Error: server_stop command takes one or more arguments. Type "help" for help.' ) return indexes = set() if len(args) == 1 and args[0] == 'all': # stop all running servers running_status = self.sc_app.get_running_status() for (i, running) in enumerate(running_status): if running: indexes.add(i) else: try: for i in args: indexes.add(int(i)) except: f'Error: server_stop bad arguments: {args}. Type "help" for help.' self.sc_app.servers_stop(indexes) def complete_server_stop(self, text, line, begidx, endidx): running_status = sc_app.get_running_status() if 'all'.startswith(text): return ['all'] return [ str(i) for (i, running) in enumerate(running_status) if running and str(i).startswith(text) ] def help_server_stop(self): print('\n'.join([ 'server_stop index [index...] | all', 'Stop a running server', ])) # server_stop ################## ################## # hook def do_hook(self, line): args = line.split() if len(args) != 2: print( f'Error: hook command takes two arguments. Type "help" for help.' ) return nickname = args[0] args.pop(0) hook_name = args[0] args.pop(0) assert not args if nickname == 'door': print(f'Error: Cannot set hooks on the "door" account.') return if not self.sc_app.is_alias(nickname): print(f'Error: {nickname} is not in the address book') return src_account = self.sc_app.account_from_alias(nickname) if hook_name not in _valid_hook_names: print( f'{hook_name} is not a valid hook. Valid hooks are: {_valid_hook_names}' ) return hook_file = HOOKS_DIR / hook_name / f'{hook_name}.wasm' if not os.path.isfile(hook_file): print(f'Error: The hook file {hook_file} does not exist.') return create_code = _file_to_hex(hook_file) self.sc_app(SetHook(account=src_account, create_code=create_code)) self.sc_app.maybe_ledger_accept() def complete_hook(self, text, line, begidx, endidx): args = line.split() arg_num = len(args) if not text: arg_num += 1 if arg_num == 2: # account return self._complete_account(text, line, chain_name='sidechain') elif arg_num == 3: # hook if not text: return _valid_hook_names return [c for c in _valid_hook_names if c.startswith(text)] return [] def help_hook(self): print('\n'.join([ 'hook account hook_name', 'Set a hook on a sidechain account', ])) # hook ################## ################## # quit def do_quit(self, arg): print('Thank you for using RiplRepl. Goodbye.\n\n') return True def help_quit(self): print('Exit the program.') # quit ################## ################## # setup_accounts def do_setup_accounts(self, arg): for a in ['alice', 'bob']: self.mc_app.create_account(a) for a in ['brad', 'carol']: self.sc_app.create_account(a) amt = Asset(value=5000 * 1_000_000) src = self.mc_app.account_from_alias('root') dst = self.mc_app.account_from_alias('alice') self.mc_app(Payment(account=src, dst=dst, amt=amt)) self.mc_app.maybe_ledger_accept() # setup_accounts ################## ################## # setup_ious def do_setup_ious(self, arg): mc_app = self.mc_app sc_app = self.sc_app mc_asset = Asset(value=0, currency='USD', issuer=mc_app.account_from_alias('root')) sc_asset = Asset(value=0, currency='USD', issuer=sc_app.account_from_alias('door')) mc_app.add_asset_alias(mc_asset, 'rrr') sc_app.add_asset_alias(sc_asset, 'ddd') mc_app( Trust(account=mc_app.account_from_alias('alice'), limit_amt=mc_asset(1_000_000))) ## create brad account on the side chain and set the trust line memos = [{ 'Memo': { 'MemoData': sc_app.account_from_alias('brad').account_id_str_as_hex() } }] mc_app( Payment(account=mc_app.account_from_alias('alice'), dst=mc_app.account_from_alias('door'), amt=Asset(value=3000 * 1_000_000), memos=memos)) mc_app.maybe_ledger_accept() # create a trust line to alice and pay her USD/rrr mc_app( Trust(account=mc_app.account_from_alias('alice'), limit_amt=mc_asset(1_000_000))) mc_app.maybe_ledger_accept() mc_app( Payment(account=mc_app.account_from_alias('root'), dst=mc_app.account_from_alias('alice'), amt=mc_asset(10_000))) mc_app.maybe_ledger_accept() time.sleep(2) # create a trust line for brad sc_app( Trust(account=sc_app.account_from_alias('brad'), limit_amt=sc_asset(1_000_000))) # setup_ious ################## ################## # q def do_q(self, arg): return self.do_quit(arg) def help_q(self): return self.help_quit() # q ################## ################## # account_tx def do_account_tx(self, line): args = line.split() if len(args) < 2: print( f'Error: account_tx command takes two or three arguments. Type "help" for help.' ) return chain = None if args[0] not in ['mainchain', 'sidechain']: print( f'Error: The first argument must be "mainchain" or "sidechain".' ) return if args[0] == 'mainchain': chain = self.mc_app else: chain = self.sc_app args.pop(0) accountStr = args[0] args.pop(0) out_file = None if args: out_file = args[0] args.pop(0) assert not args if not chain.is_alias(accountStr): print( f'Error: The issuer {issuer} is not part of the address book.') return account = chain.account_from_alias(accountStr) result = json.dumps(chain(AccountTx(account=account)), indent=1) print(f'{result}') if out_file: with open(out_file, 'a') as f: f.write(f'{result}\n') def complete_account_tx(self, text, line, begidx, endidx): args = line.split() arg_num = len(args) if not text: arg_num += 1 if arg_num == 2: # chain return self._complete_chain(text, line) if arg_num == 3: # account return self._complete_account(text, line, chain_name=args[1]) return [] def help_account_tx(self): print('\n'.join([ 'account_tx (mainchain | sidechain) account [filename]', 'Return the account transactions', ])) # account_tx ################## ################## # subscribe # Note: The callback isn't called until the user types a new command. # TODO: Make subscribe asynchronous so the callback is called without requiring the user to type # a new command. def do_subscribe(self, line): args = line.split() if len(args) != 3: print( f'Error: subscribe command takes exactly three arguments. Type "help" for help.' ) return chain = None if args[0] not in ['mainchain', 'sidechain']: print( f'Error: The first argument must be "mainchain" or "sidechain".' ) return if args[0] == 'mainchain': chain = self.mc_app else: chain = self.sc_app args.pop(0) accountStr = args[0] args.pop(0) out_file = args[0] args.pop(0) assert not args if not chain.is_alias(accountStr): print( f'Error: The issuer {issuer} is not part of the address book.') return account = chain.account_from_alias(accountStr) def _subscribe_callback(v: dict): with open(out_file, 'a') as f: f.write(f'{json.dumps(v, indent=1)}\n') chain(Subscribe(accounts=[account]), _subscribe_callback) def complete_subscribe(self, text, line, begidx, endidx): args = line.split() arg_num = len(args) if not text: arg_num += 1 if arg_num == 2: # chain return self._complete_chain(text, line) if arg_num == 3: # account return self._complete_account(text, line, chain_name=args[1]) return [] def help_subscribe(self): print('\n'.join([ 'subscribe (mainchain | sidechain) account filename', 'Subscribe to the stream and write the results to filename', 'Note: The file is not updated until the user types a new command' ])) # subscribe ################## ################## # EOF def do_EOF(self, line): print('Thank you for using RiplRepl. Goodbye.\n\n') return True def help_EOF(self): print('Exit the program by typing control-d.') # EOF ################## def repl(mc_app: App, sc_app: App): SidechainRepl(mc_app, sc_app).cmdloop()