From 0820be7b418cb5d1ebbb842630a70406ee18c01d Mon Sep 17 00:00:00 2001 From: Calin Culianu Date: Sun, 7 Apr 2019 15:51:42 +0300 Subject: [PATCH] [iOS] Updated to Python 3.6.6, plus small tweaks - Updated the 'make_ios_project' system to use Python 3.6+. Python 3.5 is going to be phased out, but still unofficially works. - iOS app has a much shorter reconnect interval on connection down; this is necessary because mobile connections are quite spotty. - iOS app has a hardcoded "preferred server only" setting internally for now. This is a security measure. Users were getting intermittent problems due to the phishers so we block them altogether on iOS. - Refactored some stuff in network.py to allow iOS to specify its own reconnect interval more easily. - On iOS we always display the TOTAL balance (confirmed + unconfirmed added together). This is less confusing to newbie users and on BCH with our reliable 0-conf, unconfirmed is not such a big deal. Unconfirmed balances are highlighted in BLUE to alert users that it's not confirmed yet, but otherwise the UI shows the total balance (conf + unconf). --- ios/ElectronCash/__main__.py | 5 ++++ ios/ElectronCash/app.py | 28 ++++++++++++++++--- .../electroncash_gui/ios_native/gui.py | 17 +++++++---- .../electroncash_gui/ios_native/wallets.py | 21 ++++++++------ ios/make_ios_project.sh | 6 ++-- ios/recompile_python.sh | 4 +-- lib/network.py | 15 ++++++---- 7 files changed, 68 insertions(+), 28 deletions(-) diff --git a/ios/ElectronCash/__main__.py b/ios/ElectronCash/__main__.py index 7a2f19bf8..76e7463d2 100644 --- a/ios/ElectronCash/__main__.py +++ b/ios/ElectronCash/__main__.py @@ -4,6 +4,11 @@ # # MIT License # +import os +# Disable google protobuf C++ implementation since we don't have the .so files +# anyway on iOS. this call isn't strictly necessary but we may as well +# do it just to be sure. +os.environ['PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION'] = 'python' # The below line needs to be here becasue the iOS main.m evaluates this script and looks for a # Python class (that is bridged to ObjC) named "PythonAppDelegate", which gets the diff --git a/ios/ElectronCash/app.py b/ios/ElectronCash/app.py index ec9e5ddd3..230ea7e50 100644 --- a/ios/ElectronCash/app.py +++ b/ios/ElectronCash/app.py @@ -8,9 +8,10 @@ from electroncash_gui.ios_native.monkeypatches import MonkeyPatches from electroncash.util import set_verbosity from electroncash_gui.ios_native import ElectrumGui -from electroncash_gui.ios_native.utils import call_later, get_user_dir, cleanup_tmp_dir, is_debug_build, NSLogSuppress +from electroncash_gui.ios_native.utils import call_later, get_user_dir, cleanup_tmp_dir, is_debug_build, NSLogSuppress, NSLog from electroncash.simple_config import SimpleConfig +# NB: This is called from appdelegate.py "application_didFinishLaunchingWithOptions_" def main(): cleanup_tmp_dir() @@ -19,6 +20,7 @@ def main(): 'cmd': 'gui', 'gui': 'ios_native', 'cwd': os.getcwd(), + 'whitelist_servers_only' : True, # on iOS we force only the whitelist ('preferred') servers only for now as a security measure } set_verbosity(config_options.get('verbose'), timestamps=False, thread_id=False) @@ -26,12 +28,30 @@ def main(): MonkeyPatches.patch() - #for k,v in config_options.items(): - # print("config[%s] = %s"%(str(k),str(v))) - config = SimpleConfig(config_options, read_user_dir_function = get_user_dir) gui = ElectrumGui(config) call_later(0.010, gui.main) # this is required for the activity indicator to actually animate. Switch to a direct call if not using activity indicator on Splash2 + _printStats(config_options) # Prints some startup/debug stats such as Python version and SSL version (this is done in another thread to hopefully not impact startup overhead too much, as importing ssl may be a bit heavy) + return "Bitcoin Cash FTW!" + +def _printStats(config_options): + import threading + def thrdfunc(config_options): + # lazy init of SSL + import ssl, sys + from electroncash import version + NSLog("Electron Cash lib version: %s (using server protocol: %s)", version.PACKAGE_VERSION, version.PROTOCOL_VERSION) + NSLog("Python version: %s", ' '.join(sys.version.split('\n'))) + NSLog("OpenSSL version: %s", ssl.OPENSSL_VERSION) + #NSLog("Environment Vars:") + #for k,v in os.environ.copy().items(): + # NSLog("%s=%s", str(k), str(v)) + #NSLog("Config Vars:") + #for k,v in config_options.copy().items(): + # NSLog("config[%s] = %s", str(k), str(v)) + # / + # We do this from a thread so as to not delay app startup by importing more stuff we don't strictly need. + threading.Thread(target=thrdfunc, args=(config_options,), daemon=True).start() diff --git a/ios/ElectronCash/electroncash_gui/ios_native/gui.py b/ios/ElectronCash/electroncash_gui/ios_native/gui.py index 20dd5685e..e51139b82 100644 --- a/ios/ElectronCash/electroncash_gui/ios_native/gui.py +++ b/ios/ElectronCash/electroncash_gui/ios_native/gui.py @@ -367,6 +367,11 @@ def register_network_callbacks(self): # methods of this class only, and specifically not be # partials, lambdas or methods of subobjects. Hence... self.daemon.network.register_callback(self.on_network, interests) + # Set the node and server retry interval to more reasonable valus for iOS + # This is because on mobile we really may have a spotty connection so + # it pays to retry often + self.daemon.network.NODES_RETRY_INTERVAL = 20 # seconds + self.daemon.network.SERVER_RETRY_INTERVAL = 3 # seconds utils.NSLog("REGISTERED NETWORK CALLBACKS") def unregister_network_callbacks(self): @@ -644,20 +649,22 @@ def on_status_update(self): walletStatus = wallets.StatusOnline networkStatusText = _("Online") c, u, x = self.wallet.get_balance() - walletBalanceTxt = self.format_amount(c) + walletBalanceTxt = self.format_amount(c+u+x) walletUnitTxt = self.base_unit() - text = _("Balance" ) + ": %s "%(self.format_amount_and_units(c)) + text = _("Balance" ) + ": %s "%(self.format_amount_and_units(c+u+x)) ux = 0 + adjective = 'unconf.' if u: - s = " [%s unconfirmed]"%(self.format_amount(u, True).strip()) + s = " [%s unconfirmed]"%(self.format_amount(u, u < 0).strip()) text += s ux += u if x: - s = " [%s unmatured]"%(self.format_amount(x, True).strip()) + s = " [%s unmatured]"%(self.format_amount(x, x < 0).strip()) text += s ux += x + adjective = 'unavail.' if ux: - walletUnconfTxt += "[%s unconf.]"%(self.format_amount(ux, True)).strip() + walletUnconfTxt += "[%s %s]"%(self.format_amount(ux, ux < 0).strip(), adjective) # [134 unconf.] # append fiat balance and price if self.daemon.fx.is_enabled(): diff --git a/ios/ElectronCash/electroncash_gui/ios_native/wallets.py b/ios/ElectronCash/electroncash_gui/ios_native/wallets.py index 428f6ad25..3d3c37dbf 100644 --- a/ios/ElectronCash/electroncash_gui/ios_native/wallets.py +++ b/ios/ElectronCash/electroncash_gui/ios_native/wallets.py @@ -159,20 +159,25 @@ def setStatus_(self, mode : int) -> None: @objc_method def setAmount_andUnits_unconf_(self, amt, units, unconf) -> None: - #ats = NSMutableAttributedString.alloc().initWithString_(units).autorelease() + ats = None if unconf: unconf = " " + unconf.strip() - '''ats.appendAttributedString_(NSAttributedString.alloc().initWithString_attributes_( - unconf, + ats = NSAttributedString.alloc().initWithString_attributes_( + amt, { - NSFontAttributeName: UIFont.systemFontOfSize_(11.0) + NSForegroundColorAttributeName: utils.uicolor_custom('nav') } - ).autorelease()) - ''' + ).autorelease() else: unconf = '' - self.walletAmount.text = amt - #self.walletUnits.attributedText = ats + if ats: + # User has unconfired balance -- show blue text + self.walletAmount.text = None + self.walletAmount.attributedText = ats + else: + # User has regular balance -- show regular text + self.walletAmount.attributedText = None + self.walletAmount.text = amt self.walletUnits.text = units+unconf if self.modalDrawerVC: self.modalDrawerVC.amount.text = amt diff --git a/ios/make_ios_project.sh b/ios/make_ios_project.sh index eb9489edc..5c33a4716 100755 --- a/ios/make_ios_project.sh +++ b/ios/make_ios_project.sh @@ -2,11 +2,11 @@ . ./common.sh -/usr/bin/env python3 --version | grep -q " 3.5" +/usr/bin/env python3 --version | grep -q " 3.[6789]" if [ "$?" != "0" ]; then if /usr/bin/env python3 --version; then - echo "WARNING:: Creating the Briefcase-based Xcode project for iOS requires Python 3.5." - echo "We will proceed anyway -- but if you get errors, try switching to Python 3.5." + echo "WARNING:: Creating the Briefcase-based Xcode project for iOS requires Python 3.6+." + echo "We will proceed anyway -- but if you get errors, try switching to Python 3.6+." else echo "ERROR: Python3+ is required" exit 1 diff --git a/ios/recompile_python.sh b/ios/recompile_python.sh index f00070760..919be991b 100755 --- a/ios/recompile_python.sh +++ b/ios/recompile_python.sh @@ -23,10 +23,10 @@ elif [ -n "$1" ]; then else originaldir=`pwd` cd "$dir1" - python3.5 -O -m compileall . || exit 1 + python3 -O -m compileall . || exit 1 cd "$originaldir" cd "$dir2" - python3.5 -O -m compileall . || exit 1 + python3 -O -m compileall . || exit 1 cd "$originaldir" fi diff --git a/lib/network.py b/lib/network.py index 9e74ec03e..2fceb46a6 100644 --- a/lib/network.py +++ b/lib/network.py @@ -46,9 +46,7 @@ DEFAULT_AUTO_CONNECT = True -NODES_RETRY_INTERVAL = 60 -SERVER_RETRY_INTERVAL = 10 - +DEFAULT_WHITELIST_SERVERS_ONLY = True def parse_servers(result): """ parse servers list into dict format""" @@ -200,6 +198,11 @@ class Network(util.DaemonThread): INSTANCE = None # Only 1 Network instance is ever alive during app lifetime (it's a singleton) + # These defaults are decent for the desktop app. Other platforms may + # override these at any time (iOS sets these to lower values). + NODES_RETRY_INTERVAL = 60 # How often to retry a node we know about in secs, if we are connected to less than 10 nodes + SERVER_RETRY_INTERVAL = 10 # How often to reconnect when server down in secs + def __init__(self, config=None): if config is None: config = {} # Do not use mutables as default values! @@ -957,7 +960,7 @@ def maintain_sockets(self): server_count = len(self.interfaces) + len(self.connecting) if server_count < self.num_server: self.start_random_interface() - if now - self.nodes_retry_time > NODES_RETRY_INTERVAL: + if now - self.nodes_retry_time > self.NODES_RETRY_INTERVAL: self.print_error('network: retrying connections') self.disconnected_servers = set([]) self.nodes_retry_time = now @@ -970,7 +973,7 @@ def maintain_sockets(self): self.switch_to_random_interface() else: if self.default_server in self.disconnected_servers: - if now - self.server_retry_time > SERVER_RETRY_INTERVAL: + if now - self.server_retry_time > self.SERVER_RETRY_INTERVAL: self.disconnected_servers.remove(self.default_server) self.server_retry_time = now else: @@ -1858,7 +1861,7 @@ def _compute_whitelist(self): ret -= set(self.config.get('server_whitelist_removed', [])) # this key is all the servers that were hardcoded in the whitelist that the user explicitly removed return ret, servers_to_hostmap(ret) - def is_whitelist_only(self): return bool(self.config.get('whitelist_servers_only', True)) + def is_whitelist_only(self): return bool(self.config.get('whitelist_servers_only', DEFAULT_WHITELIST_SERVERS_ONLY)) def set_whitelist_only(self, b): if bool(b) == self.is_whitelist_only():