def test_valid_address(self): """Test address validation""" # Null should always be false self.assertFalse(Validators.is_valid_address(None)) os.environ['BANANO'] = '1' # Valid self.assertTrue( Validators.is_valid_address( 'ban_1bananobh5rat99qfgt1ptpieie5swmoth87thi74qgbfrij7dcgjiij94xr' )) # Bad checksum self.assertFalse( Validators.is_valid_address( 'ban_1bananobh5rat99qfgt1ptpieie5swmoth87thi74qgbfrij7dcgjiij94xa' )) # Bad length self.assertFalse( Validators.is_valid_address( 'ban_1bananobh5rat99qfgt1ptpieie5swmoth87thi74qgbfrij7dcgjiij94x' )) del os.environ['BANANO'] # Valid self.assertTrue( Validators.is_valid_address( 'nano_1bananobh5rat99qfgt1ptpieie5swmoth87thi74qgbfrij7dcgjiij94xr' )) # Bad checksum self.assertFalse( Validators.is_valid_address( 'nano_1bananobh5rat99qfgt1ptpieie5swmoth87thi74qgbfrij7dcgjiij94xa' )) # Bad length self.assertFalse( Validators.is_valid_address( 'nano_1bananobh5rat99qfgt1ptpieie5swmoth87thi74qgbfrij7dcgjiij94x' )) # Valid self.assertTrue( Validators.is_valid_address( 'xrb_1bananobh5rat99qfgt1ptpieie5swmoth87thi74qgbfrij7dcgjiij94xr' )) # Bad checksum self.assertFalse( Validators.is_valid_address( 'xrb_1bananobh5rat99qfgt1ptpieie5swmoth87thi74qgbfrij7dcgjiij94xa' )) # Bad length self.assertFalse( Validators.is_valid_address( 'xrb_1bananobh5rat99qfgt1ptpieie5swmoth87thi74qgbfrij7dcgjiij94x' ))
def test_valid_block_hash(self): # None type self.assertFalse(Validators.is_valid_block_hash(None)) # Non-hex strings self.assertFalse(Validators.is_valid_block_hash('1234')) self.assertFalse(Validators.is_valid_block_hash('Appditto LLC')) self.assertFalse( Validators.is_valid_block_hash( 'ban_1bananobh5rat99qfgt1ptpieie5swmoth87thi74qgbfrij7dcgjiij94xr' )) # Too short self.assertFalse( Validators.is_valid_block_hash( '5E5B7C8F97BDA8B90FAA243050D99647F80C25EB4A07E7247114CBB129BDA18' )) # Too long self.assertFalse( Validators.is_valid_block_hash( '5E5B7C8F97BDA8B90FAA243050D99647F80C25EB4A07E7247114CBB129BDA1888' )) # Non-hex character self.assertFalse( Validators.is_valid_block_hash( '5E5B7C8F97BDA8B90FAA243050D99647F80C25EB4A07E7247114CBB129BDA18Z' )) # Valid self.assertTrue( Validators.is_valid_block_hash( '5E5B7C8F97BDA8B90FAA243050D99647F80C25EB4A07E7247114CBB129BDA188' ))
async def send(self, request: web.Request, request_json: dict): """RPC send""" if 'wallet' not in request_json or 'source' not in request_json or 'destination' not in request_json or 'amount' not in request_json: return self.generic_error() elif not Validators.is_valid_address(request_json['source']): return self.json_response(data={'error': 'Invalid source'}) elif not Validators.is_valid_address(request_json['destination']): return self.json_response(data={'error': 'Invalid destination'}) id = request_json['id'] if 'id' in request_json else None work = request_json['work'] if 'work' in request_json else None # Retrieve wallet try: wallet = await Wallet.get_wallet(request_json['wallet']) except WalletNotFound: return self.json_response(data={'error': 'wallet not found'}) except WalletLocked: return self.json_response(data={'error': 'wallet locked'}) # Retrieve account on wallet account = await wallet.get_account(request_json['source']) if account is None: return self.json_response(data={'error': 'Account not found'}) # Try to create and publish send block wallet = WalletUtil(account, wallet) try: resp = await wallet.send(int(request_json['amount']), request_json['destination'], id=id, work=work) except AccountNotFound: return self.json_response(data={'error': 'Account not found'}) except BlockNotFound: return self.json_response(data={'error': 'Block not found'}) except WorkFailed: return self.json_response( data={'error': 'Failed to generate work'}) except ProcessFailed: return self.json_response(data={'error': 'RPC Process failed'}) except InsufficientBalance: return self.json_response(data={'error': 'insufficient balance'}) if resp is None: return self.json_response( data={'error': 'Unable to create send block'}) return self.json_response(data=resp)
async def wallet_change_seed(self, request: web.Request, request_json: dict): """RPC wallet_change_seed""" if 'wallet' not in request_json or 'seed' not in request_json: return self.generic_error() elif not Validators.is_valid_block_hash(request_json['seed']): return self.json_response(data={'error': 'Invalid seed'}) # Retrieve wallet try: wallet = await Wallet.get_wallet(request_json['wallet']) except WalletNotFound: return self.json_response(data={'error': 'wallet not found'}) except WalletLocked: return self.json_response(data={'error': 'wallet locked'}) # Reset password if wallet.encrypted: await wallet.encrypt_wallet('') # Change key await wallet.change_seed(request_json['seed']) # Get newest account newest = await wallet.get_newest_account() return self.json_response( data={ "success": "", "last_restored_account": newest.address, "restored_count": newest.account_index + 1 })
async def wallet_representative_set(self, request: web.Request, request_json: dict): """RPC wallet_representative_set""" if 'wallet' not in request_json or 'representative' not in request_json or ( 'update_existing_accounts' in request_json and not isinstance( request_json['update_existing_accounts'], bool)): return self.generic_error() elif not Validators.is_valid_address(request_json['representative']): return self.json_response(data={'error': 'Invalid address'}) update_existing = False if 'update_existing_accounts' in request_json: update_existing = request_json['update_existing_accounts'] # Retrieve wallet try: wallet = await Wallet.get_wallet(request_json['wallet']) except WalletNotFound: return self.json_response(data={'error': 'wallet not found'}) except WalletLocked: return self.json_response(data={'error': 'wallet locked'}) wallet.representative = request_json['representative'] await wallet.save(update_fields=['representative']) if update_existing: await wallet.bulk_representative_update( request_json['representative']) return self.json_response(data={'set': '1'})
async def wallet_representative_set(wallet_id: str, rep: str, update_existing: bool = False): # Retrieve wallet # Retrieve wallet crypt = None password=None if not Validators.is_valid_address(rep): print("Invalid representative") exit(1) try: wallet = await Wallet.get_wallet(wallet_id) except WalletNotFound: print(f"No wallet found with ID: {wallet_id}") exit(1) except WalletLocked as wl: wallet = wl.wallet if update_existing: while True: try: npass = getpass.getpass(prompt='Enter current password to decrypt wallet:') crypt = AESCrypt(npass) try: decrypted = crypt.decrypt(wl.wallet.seed) wallet = wl.wallet wallet.seed = decrypted password=npass except DecryptionError: print("**Invalid password**") except KeyboardInterrupt: break exit(0) wallet.representative = rep await wallet.save(update_fields=['representative']) await wallet.bulk_representative_update(rep) print(f"Representative changed")
async def account_representative_set(self, request: web.Request, request_json: dict): """RPC account_representative_set""" if 'wallet' not in request_json or 'account' not in request_json or 'representative' not in request_json: return self.generic_error() elif not Validators.is_valid_address(request_json['account']): return self.json_response(data={'error': 'Invalid account'}) elif not Validators.is_valid_address(request_json['representative']): return self.json_response(data={'error': 'Invalid representative'}) work = request_json['work'] if 'work' in request_json else None # Retrieve wallet try: wallet = await Wallet.get_wallet(request_json['wallet']) except WalletNotFound: return self.json_response(data={'error': 'wallet not found'}) except WalletLocked: return self.json_response(data={'error': 'wallet locked'}) # Retrieve account on wallet account = await wallet.get_account(request_json['account']) if account is None: return self.json_response(data={'error': 'Account not found'}) # Try to create and publish CHANGE block wallet = WalletUtil(account, wallet) try: resp = await wallet.representative_set( request_json['representative'], work=work) except AccountNotFound: return self.json_response(data={'error': 'Account not found'}) except WorkFailed: return self.json_response( data={'error': 'Failed to generate work'}) except ProcessFailed: return self.json_response(data={'error': 'RPC Process failed'}) if resp is None: return self.json_response( data={'error': 'Unable to create change block'}) return self.json_response(data=resp)
async def receive(self, request: web.Request, request_json: dict): """RPC receive""" if 'wallet' not in request_json or 'account' not in request_json or 'block' not in request_json: return self.generic_error() elif not Validators.is_valid_address(request_json['account']): return self.json_response(data={'error': 'Invalid address'}) elif not Validators.is_valid_block_hash(request_json['block']): return self.json_response(data={'error': 'Invalid block'}) work = request_json['work'] if 'work' in request_json else None # Retrieve wallet try: wallet = await Wallet.get_wallet(request_json['wallet']) except WalletNotFound: return self.json_response(data={'error': 'wallet not found'}) except WalletLocked: return self.json_response(data={'error': 'wallet locked'}) # Retrieve account on wallet account = await wallet.get_account(request_json['account']) if account is None: return self.json_response(data={'error': 'Account not found'}) # Try to receive block wallet = WalletUtil(account, wallet) try: response = await wallet.receive(request_json['block'], work=work) except BlockNotFound: return self.json_response(data={'error': 'Block not found'}) except WorkFailed: return self.json_response( data={'error': 'Failed to generate work'}) except ProcessFailed: return self.json_response(data={'error': 'RPC Process failed'}) if response is None: return self.json_response( data={'error': 'Unable to receive block'}) return self.json_response(data=response)
async def wallet_create(self, request: web.Request, request_json: dict): """Route for creating new wallet""" if 'seed' in request_json: if not Validators.is_valid_block_hash(request_json['seed']): return self.json_response(data={'error': 'Invalid seed'}) new_seed = request_json['seed'] else: new_seed = RandomUtil.generate_seed() async with in_transaction() as conn: wallet = Wallet(seed=new_seed) await wallet.save(using_db=conn) await wallet.account_create(using_db=conn) return self.json_response(data={'wallet': str(wallet.id)})
async def wallet_contains(self, request: web.Request, request_json: dict): """RPC wallet_contains""" if 'wallet' not in request_json or 'account' not in request_json: return self.generic_error() elif not Validators.is_valid_address(request_json['account']): return self.json_response(data={'error': 'Invalid account'}) # Retrieve wallet try: wallet = await Wallet.get_wallet(request_json['wallet']) except WalletNotFound: return self.json_response(data={'error': 'wallet not found'}) except WalletLocked: return self.json_response(data={'error': 'wallet locked'}) exists = (await wallet.get_account(request_json['account'])) is not None return self.json_response(data={'exists': '1' if exists else '0'})
def instance(cls) -> 'Config': if cls._instance is None: cls._instance = cls.__new__(cls) try: with open(f"{Utils.get_project_root().joinpath(pathlib.PurePath('config.yaml'))}", "r") as in_yaml: cls.yaml = list(yaml.load_all(in_yaml, Loader=yaml.FullLoader))[0] except FileNotFoundError: cls.yaml = None # Parse options cls.banano = cls.get_yaml_property('wallet', 'banano', False) cls.log_file = cls.get_yaml_property('server', 'log_file', default='/tmp/pippin_wallet.log') cls.debug = cls.get_yaml_property('server', 'debug', default=False) cls.stdout = cls.get_yaml_property('server', 'log_to_stdout', default=False) cls.node_url = cls.get_yaml_property('server', 'node_rpc_url', default='http://[::1]:7072' if cls.banano else 'http://[::1]:7076') cls.node_ws_url = cls.get_yaml_property('server', 'node_ws_url', None) cls.port = cls.get_yaml_property('server', 'port', default=11338) cls.host = cls.get_yaml_property('server', 'host', default='127.0.0.1') cls.work_peers = cls.get_yaml_property('wallet', 'work_peers', []) cls.node_work_generate = cls.get_yaml_property('wallet', 'node_work_generate', False) cls.receive_minimum = cls.get_yaml_property('wallet', 'receive_minimum', 1000000000000000000000000000 if cls.banano else 1000000000000000000000000) cls.auto_receive_on_send = cls.get_yaml_property('wallet', 'auto_receive_on_send', True) cls.max_work_processes = cls.get_yaml_property('wallet', 'max_work_processes', 1) cls.max_sign_threads = cls.get_yaml_property('wallet', 'max_sign_threads', 1) if not cls.banano: cls.preconfigured_reps = cls.get_yaml_property('wallet', 'preconfigured_representatives_nano', default=None) else: cls.preconfigured_reps = cls.get_yaml_property('wallet', 'preconfigured_representatives_banano', default=None) # Enforce that all reps are valid if cls.preconfigured_reps is not None: cls.preconfigured_reps = set(cls.preconfigured_reps) for r in cls.preconfigured_reps: if not Validators.is_valid_address(r): log.server_logger.warn(f"{r} is not a valid representative!") cls.preconfigured_reps.remove(r) if len(cls.preconfigured_reps) == 0: cls.preconfigured_reps = None # Go to default if None if cls.preconfigured_reps is None: cls.preconfigured_reps = DEFAULT_BANANO_REPS if cls.banano else DEFAULT_NANO_REPS return cls._instance
async def work_generate(self, request: web.Request, request_json: dict): """Route for running work_generate""" if 'hash' in request_json: if not Validators.is_valid_block_hash(request_json['hash']): return self.json_response(data={'error': 'Invalid hash'}) else: return self.generic_error() difficulty = DifficultyModel.instance().send_difficulty if 'difficulty' in request_json: difficulty = request_json['difficulty'] elif 'subtype' in request_json and request_json['subtype'] == 'receive': difficulty = DifficultyModel.instance().receive_difficulty # Generate work work = await WorkClient.instance().work_generate( request_json['hash'], difficulty) if work is None: return self.json_response( data={'error': 'Failed to generate work'}) return self.json_response(data={'work': work})
async def wallet_add(self, request: web.Request, request_json: dict): """RPC wallet_add""" if 'wallet' not in request_json or 'key' not in request_json: return self.generic_error() elif not Validators.is_valid_block_hash(request_json['key']): return self.json_response(data={'error': 'Invalid key'}) # Retrieve wallet try: wallet = await Wallet.get_wallet(request_json['wallet']) except WalletNotFound: return self.json_response(data={'error': 'wallet not found'}) except WalletLocked: return self.json_response(data={'error': 'wallet locked'}) # Add account try: address = await wallet.adhoc_account_create(request_json['key']) except AccountAlreadyExists: return self.json_response(data={'error': 'account already exists'}) return self.json_response(data={'account': address})
def main(): loop = asyncio.new_event_loop() try: loop.run_until_complete(DBConfig().init_db()) if options.command == 'wallet_list': loop.run_until_complete(wallet_list()) elif options.command == 'wallet_create': if options.seed is not None: if not Validators.is_valid_block_hash(options.seed): print("Invalid seed specified") exit(1) loop.run_until_complete(wallet_create(options.seed)) elif options.command == 'wallet_change_seed': if options.seed is not None: if not Validators.is_valid_block_hash(options.seed): print("Invalid seed specified") exit(1) else: while True: try: options.seed = getpass.getpass(prompt='Enter new wallet seed:') if Validators.is_valid_block_hash(options.seed): break print("**Invalid seed**, should be a 64-character hex string") except KeyboardInterrupt: break exit(0) password = '' if options.encrypt: while True: try: password = getpass.getpass(prompt='Enter password to encrypt wallet:') if password.strip() == '': print("**Bad password** - cannot be blanke") break except KeyboardInterrupt: break exit(0) loop.run_until_complete(wallet_change_seed(options.wallet, options.seed, password)) elif options.command == 'wallet_view_seed': loop.run_until_complete(wallet_view_seed(options.wallet, options.password, options.all_keys)) elif options.command == 'account_create': if options.key is not None: if not Validators.is_valid_block_hash(options.key): print("Invalid Private Key") exit(0) elif options.key is not None and options.count is not None: print("You can only specify one: --key or --count") print("--count can only be used for deterministic accounts") elif options.count is not None: if options.count < 1: print("Count needs to be at least 1...") loop.run_until_complete(account_create(options.wallet, options.key, options.count)) elif options.command == 'wallet_destroy': loop.run_until_complete(wallet_destroy(options.wallet)) elif options.command == 'wallet_representative_get': loop.run_until_complete(wallet_representative_get(options.wallet)) elif options.command == 'wallet_representative_set': loop.run_until_complete(wallet_representative_set(options.wallet, options.representatives, update_existing=options.update_existing)) else: parser.print_help() except Exception as e: print(str(e)) raise e finally: loop.run_until_complete(Tortoise.close_connections()) loop.close()