From 35f47ea2a718e5a83e51480879c6df06ecad2355 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Tue, 11 Sep 2018 22:54:07 +0200 Subject: [PATCH 01/10] Updates new v20180617 release APK direct link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d58bbd6..72136f0 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://secure.travis-ci.org/AndreMiras/EtherollApp.png?branch=develop)](http://travis-ci.org/AndreMiras/EtherollApp) - + Provably fair dice game running on the [Ethereum blockchain](https://etheroll.com/#/smart-contract). Built with Python, [Kivy](https://github.com/kivy/kivy) and love. From 55348e7124612afc7951ae7864d74b5d58362b47 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 9 Sep 2018 17:26:14 +0200 Subject: [PATCH 02/10] Restarts killed roll pulling service, fixes #103 Uses `Service.setAutoRestartService()` to restart service when killed, refs https://github.com/kivy/python-for-android/pull/1374 --- src/service/main.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/service/main.py b/src/service/main.py index 8670102..2d6f59b 100644 --- a/src/service/main.py +++ b/src/service/main.py @@ -2,6 +2,7 @@ from time import sleep from kivy.storage.jsonstore import JsonStore +from kivy.utils import platform from plyer import notification from ethereum_utils import AccountUtils @@ -31,6 +32,17 @@ def start(self): self.pull_accounts_rolls() sleep(PULL_FREQUENCY_SECONDS) + @staticmethod + def set_auto_restart_service(restart=True): + """ + Makes sure the service restarts automatically on Android when killed. + """ + if platform != 'android': + return + from jnius import autoclass + PythonService = autoclass('org.kivy.android.PythonService') + PythonService.mService.setAutoRestartService(restart) + @property def pyetheroll(self): """ @@ -158,6 +170,7 @@ def do_notify(self, merged_logs): def main(): service = MonitorRollsService() + service.set_auto_restart_service() service.start() From 03dabddb8003df31832b5bdad842ee38e7ce99a0 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 16 Sep 2018 13:10:11 +0200 Subject: [PATCH 03/10] Shuts down service after inactivity If there's no roll activity, the service will stop the pulling loop. This is to save smartphone resources (bandwidth, CPU, battery...). Placing another bet will restart the service if it's down. However if the service is still up it will not restart. This could be an issue since the activity counter won't be reset. In another design we could use interprocess commmunication to update the activity counter or restart the service if down. --- src/CHANGELOG.md | 2 +- src/etheroll/controller.py | 21 ++++----------------- src/service/__init__.py | 0 src/service/main.py | 19 ++++++++++++++----- src/service/utils.py | 20 ++++++++++++++++++++ 5 files changed, 39 insertions(+), 23 deletions(-) create mode 100644 src/service/__init__.py create mode 100644 src/service/utils.py diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md index 8ef1ca2..0feedc4 100644 --- a/src/CHANGELOG.md +++ b/src/CHANGELOG.md @@ -3,7 +3,7 @@ ## [v20180911] - Fix pycryptodome compilation, refs #99 - - Notifie when roll processed, refs #57 + - Notify when roll processed, refs #57, #103 - Update to new contract address ## [v20180617] diff --git a/src/etheroll/controller.py b/src/etheroll/controller.py index 0cc4d9a..2f88190 100755 --- a/src/etheroll/controller.py +++ b/src/etheroll/controller.py @@ -18,6 +18,7 @@ from etheroll.settings import SettingsScreen from etheroll.switchaccount import SwitchAccountScreen from etheroll.utils import Dialog, load_kv_from_py, run_in_thread +from service.utils import start_service from version import __version__ patch_find_library_android() @@ -357,6 +358,8 @@ def roll(self): if password is not None: self.player_roll_dice( bet_size, chances, wallet_path, password, gas_price) + # restarts roll pulling service to reset the roll activity period + start_service() def load_switch_account(self): """ @@ -476,25 +479,9 @@ def build(self): self.icon = "docs/images/icon.png" self.theme_cls.theme_style = 'Dark' self.theme_cls.primary_palette = 'Indigo' - self.start_service() + start_service() return Controller() - def start_service(self): - """ - Starts the roll pulling service. - """ - if platform == 'android': - from jnius import autoclass - package_name = 'etheroll' - package_domain = 'com.github.andremiras' - service_name = 'service' - service_class = '{}.{}.Service{}'.format( - package_domain, package_name, service_name.title()) - service = autoclass(service_class) - mActivity = autoclass('org.kivy.android.PythonActivity').mActivity - argument = '' - service.start(mActivity, argument) - def main(): # only send Android errors to Sentry diff --git a/src/service/__init__.py b/src/service/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/service/main.py b/src/service/main.py index 2d6f59b..fda301a 100644 --- a/src/service/main.py +++ b/src/service/main.py @@ -1,5 +1,5 @@ import os -from time import sleep +from time import sleep, time from kivy.storage.jsonstore import JsonStore from kivy.utils import platform @@ -13,6 +13,8 @@ patch_find_library_android() PULL_FREQUENCY_SECONDS = 10 +# time before the service shuts down if no roll activity +NO_ROLL_ACTIVITY_PERDIOD_SECONDS = 5 * 60 class MonitorRollsService(): @@ -23,14 +25,21 @@ def __init__(self): self._pyetheroll = None # per address cached merged logs, used to compare with next pulls self.merged_logs = {} + self.last_roll_activity = None - def start(self): + def run(self): """ Blocking pull loop call. + Service will stop after a period of time with no roll activity. """ - while True: + self.last_roll_activity = time() + elapsed = (time() - self.last_roll_activity) + while elapsed < NO_ROLL_ACTIVITY_PERDIOD_SECONDS: self.pull_accounts_rolls() sleep(PULL_FREQUENCY_SECONDS) + elapsed = (time() - self.last_roll_activity) + # service decided to die naturally after no roll activity + self.set_auto_restart_service(False) @staticmethod def set_auto_restart_service(restart=True): @@ -127,13 +136,13 @@ def pull_account_rolls(self, account): # since it differs, updates the cache and notifies self.merged_logs[address] = merged_logs self.do_notify(merged_logs) + self.last_roll_activity = time() def pull_accounts_rolls(self): accounts = self.account_utils.get_account_list() for account in accounts: self.pull_account_rolls(account) - # TODO: should we show "dice_result sign roll_under" and/or value won/lost? def do_notify(self, merged_logs): """ Notifies the with last roll. @@ -171,7 +180,7 @@ def do_notify(self, merged_logs): def main(): service = MonitorRollsService() service.set_auto_restart_service() - service.start() + service.run() if __name__ == '__main__': diff --git a/src/service/utils.py b/src/service/utils.py new file mode 100644 index 0000000..991a0de --- /dev/null +++ b/src/service/utils.py @@ -0,0 +1,20 @@ +from kivy.utils import platform + + +def start_service(): + """ + Starts the roll pulling service. + If the service is already running, it won't be started twice. + """ + if platform != 'android': + return + from jnius import autoclass + package_name = 'etheroll' + package_domain = 'com.github.andremiras' + service_name = 'service' + service_class = '{}.{}.Service{}'.format( + package_domain, package_name, service_name.title()) + service = autoclass(service_class) + mActivity = autoclass('org.kivy.android.PythonActivity').mActivity + argument = '' + service.start(mActivity, argument) From 7155ea0e75135fbbd3ffe789cb371b0bd893af96 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 16 Sep 2018 15:41:11 +0200 Subject: [PATCH 04/10] Recipes LDSHARED & CFLAGS cleaning, fixes #104 Since https://github.com/kivy/python-for-android/pull/1361 was merged upstream, some `LDSHARED` and `CFLAGS` could be cleaned. --- src/CHANGELOG.md | 10 +++++++++- src/python-for-android/recipes/cffi/__init__.py | 5 ----- src/python-for-android/recipes/coincurve/__init__.py | 5 ----- src/python-for-android/recipes/cytoolz/__init__.py | 5 ----- src/python-for-android/recipes/greenlet/__init__.py | 5 ----- src/python-for-android/recipes/lru-dict/__init__.py | 5 ----- .../recipes/pycryptodome/__init__.py | 2 -- src/python-for-android/recipes/pyethash/__init__.py | 4 +--- src/python-for-android/recipes/pysha3/__init__.py | 6 +----- src/python-for-android/recipes/scrypt/__init__.py | 3 --- src/python-for-android/recipes/secp256k1/__init__.py | 7 ------- 11 files changed, 11 insertions(+), 46 deletions(-) diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md index 0feedc4..8aeb3b2 100644 --- a/src/CHANGELOG.md +++ b/src/CHANGELOG.md @@ -1,15 +1,23 @@ # Change Log + +## [Unreleased] + + - Notify when roll processed, refs #57, #103 + - Recipes LDSHARED & CFLAGS cleaning, refs #104 + + ## [v20180911] - Fix pycryptodome compilation, refs #99 - - Notify when roll processed, refs #57, #103 - Update to new contract address + ## [v20180617] - Fix crash when deployed on SD, refs #96 + ## [v20180531] - Handle wrong password error, refs #9 diff --git a/src/python-for-android/recipes/cffi/__init__.py b/src/python-for-android/recipes/cffi/__init__.py index dc8c705..21ba4b6 100644 --- a/src/python-for-android/recipes/cffi/__init__.py +++ b/src/python-for-android/recipes/cffi/__init__.py @@ -16,8 +16,6 @@ class CffiRecipe(CompiledComponentsPythonRecipe): def get_recipe_env(self, arch=None): env = super(CffiRecipe, self).get_recipe_env(arch) - # sets linker to use the correct gcc (cross compiler) - env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions' libffi = self.get_recipe('libffi', self.ctx) includes = libffi.get_include_dirs(arch) env['CFLAGS'] = ' -I'.join([env.get('CFLAGS', '')] + includes) @@ -39,9 +37,6 @@ def get_recipe_env(self, arch=None): ndk_dir_python = os.path.join(self.ctx.ndk_dir, 'sources/python/', python_version) env['LDFLAGS'] += ' -L{}'.format(os.path.join(ndk_dir_python, 'libs', arch.arch)) env['LDFLAGS'] += ' -lpython{}m'.format(python_version) - # until `pythonforandroid/archs.py` gets merged upstream: - # https://github.com/kivy/python-for-android/pull/1250/files#diff-569e13021e33ced8b54385f55b49cbe6 - env['CFLAGS'] += ' -I{}/include/python/'.format(ndk_dir_python) return env diff --git a/src/python-for-android/recipes/coincurve/__init__.py b/src/python-for-android/recipes/coincurve/__init__.py index 7a57c71..6803bda 100644 --- a/src/python-for-android/recipes/coincurve/__init__.py +++ b/src/python-for-android/recipes/coincurve/__init__.py @@ -14,8 +14,6 @@ class CoincurveRecipe(PythonRecipe): def get_recipe_env(self, arch=None, with_flags_in_cc=True): env = super(CoincurveRecipe, self).get_recipe_env(arch, with_flags_in_cc) - # sets linker to use the correct gcc (cross compiler) - env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions' libsecp256k1 = self.get_recipe('libsecp256k1', self.ctx) libsecp256k1_dir = libsecp256k1.get_build_dir(arch.arch) env['CFLAGS'] += ' -I' + os.path.join(libsecp256k1_dir, 'include') @@ -26,9 +24,6 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True): ndk_dir_python = os.path.join(self.ctx.ndk_dir, 'sources/python/', python_version) env['LDFLAGS'] += ' -L{}'.format(os.path.join(ndk_dir_python, 'libs', arch.arch)) env['LDFLAGS'] += ' -lpython{}m'.format(python_version) - # until `pythonforandroid/archs.py` gets merged upstream: - # https://github.com/kivy/python-for-android/pull/1250/files#diff-569e13021e33ced8b54385f55b49cbe6 - env['CFLAGS'] += ' -I{}/include/python/'.format(ndk_dir_python) env['LDFLAGS'] += " -lsecp256k1" return env diff --git a/src/python-for-android/recipes/cytoolz/__init__.py b/src/python-for-android/recipes/cytoolz/__init__.py index 4a2550e..3454376 100644 --- a/src/python-for-android/recipes/cytoolz/__init__.py +++ b/src/python-for-android/recipes/cytoolz/__init__.py @@ -10,8 +10,6 @@ class CytoolzRecipe(PythonRecipe): def get_recipe_env(self, arch=None, with_flags_in_cc=True): env = super(CytoolzRecipe, self).get_recipe_env(arch, with_flags_in_cc) - # sets linker to use the correct gcc (cross compiler) - env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions' # required additional library and path for Crystax if self.ctx.ndk == 'crystax': # only keeps major.minor (discards patch) @@ -19,9 +17,6 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True): ndk_dir_python = os.path.join(self.ctx.ndk_dir, 'sources/python/', python_version) env['LDFLAGS'] += ' -L{}'.format(os.path.join(ndk_dir_python, 'libs', arch.arch)) env['LDFLAGS'] += ' -lpython{}m'.format(python_version) - # until `pythonforandroid/archs.py` gets merged upstream: - # https://github.com/kivy/python-for-android/pull/1250/files#diff-569e13021e33ced8b54385f55b49cbe6 - env['CFLAGS'] += ' -I{}/include/python/'.format(ndk_dir_python) return env diff --git a/src/python-for-android/recipes/greenlet/__init__.py b/src/python-for-android/recipes/greenlet/__init__.py index a8fcc00..65c9c4d 100644 --- a/src/python-for-android/recipes/greenlet/__init__.py +++ b/src/python-for-android/recipes/greenlet/__init__.py @@ -9,8 +9,6 @@ class GreenletRecipe(PythonRecipe): def get_recipe_env(self, arch=None, with_flags_in_cc=True): env = super(GreenletRecipe, self).get_recipe_env(arch, with_flags_in_cc) - # sets linker to use the correct gcc (cross compiler) - env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions' # required additional library and path for Crystax if self.ctx.ndk == 'crystax': # only keeps major.minor (discards patch) @@ -18,9 +16,6 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True): ndk_dir_python = os.path.join(self.ctx.ndk_dir, 'sources/python/', python_version) env['LDFLAGS'] += ' -L{}'.format(os.path.join(ndk_dir_python, 'libs', arch.arch)) env['LDFLAGS'] += ' -lpython{}m'.format(python_version) - # until `pythonforandroid/archs.py` gets merged upstream: - # https://github.com/kivy/python-for-android/pull/1250/files#diff-569e13021e33ced8b54385f55b49cbe6 - env['CFLAGS'] += ' -I{}/include/python/'.format(ndk_dir_python) return env diff --git a/src/python-for-android/recipes/lru-dict/__init__.py b/src/python-for-android/recipes/lru-dict/__init__.py index 2ab847d..ca88d66 100644 --- a/src/python-for-android/recipes/lru-dict/__init__.py +++ b/src/python-for-android/recipes/lru-dict/__init__.py @@ -9,8 +9,6 @@ class LruDictRecipe(PythonRecipe): def get_recipe_env(self, arch=None, with_flags_in_cc=True): env = super(LruDictRecipe, self).get_recipe_env(arch, with_flags_in_cc) - # sets linker to use the correct gcc (cross compiler) - env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions' # required additional library and path for Crystax if self.ctx.ndk == 'crystax': # only keeps major.minor (discards patch) @@ -18,9 +16,6 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True): ndk_dir_python = os.path.join(self.ctx.ndk_dir, 'sources/python/', python_version) env['LDFLAGS'] += ' -L{}'.format(os.path.join(ndk_dir_python, 'libs', arch.arch)) env['LDFLAGS'] += ' -lpython{}m'.format(python_version) - # until `pythonforandroid/archs.py` gets merged upstream: - # https://github.com/kivy/python-for-android/pull/1250/files#diff-569e13021e33ced8b54385f55b49cbe6 - env['CFLAGS'] += ' -I{}/include/python/'.format(ndk_dir_python) return env diff --git a/src/python-for-android/recipes/pycryptodome/__init__.py b/src/python-for-android/recipes/pycryptodome/__init__.py index 002977a..894e5db 100644 --- a/src/python-for-android/recipes/pycryptodome/__init__.py +++ b/src/python-for-android/recipes/pycryptodome/__init__.py @@ -10,8 +10,6 @@ class PycryptodomeRecipe(PythonRecipe): def get_recipe_env(self, arch=None, with_flags_in_cc=True): env = super(PycryptodomeRecipe, self).get_recipe_env(arch, with_flags_in_cc) - # sets linker to use the correct gcc (cross compiler) - env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions' env['LDFLAGS'] += ' -L{}'.format(os.path.join(self.ctx.bootstrap.build_dir, 'libs', arch.arch)) return env diff --git a/src/python-for-android/recipes/pyethash/__init__.py b/src/python-for-android/recipes/pyethash/__init__.py index c5bd6f7..6877ffe 100644 --- a/src/python-for-android/recipes/pyethash/__init__.py +++ b/src/python-for-android/recipes/pyethash/__init__.py @@ -9,10 +9,8 @@ class PyethashRecipe(PythonRecipe): def get_recipe_env(self, arch=None, with_flags_in_cc=True): env = super(PyethashRecipe, self).get_recipe_env(arch, with_flags_in_cc) - # sets linker to use the correct gcc (cross compiler) - env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions' # CFLAGS may only be used to specify C compiler flags, for macro definitions use CPPFLAGS - env['CPPFLAGS'] = env['CFLAGS'] + ' -I{}/sources/python/3.5/include/python/'.format(self.ctx.ndk_dir) + env['CPPFLAGS'] = env['CFLAGS'] env['CFLAGS'] = '' # LDFLAGS may only be used to specify linker flags, for libraries use LIBS env['LDFLAGS'] = env['LDFLAGS'].replace('-lm', '').replace('-lcrystax', '') diff --git a/src/python-for-android/recipes/pysha3/__init__.py b/src/python-for-android/recipes/pysha3/__init__.py index df002fd..c3120dd 100644 --- a/src/python-for-android/recipes/pysha3/__init__.py +++ b/src/python-for-android/recipes/pysha3/__init__.py @@ -2,7 +2,6 @@ from pythonforandroid.recipe import PythonRecipe -# TODO: CompiledComponentsPythonRecipe class Pysha3Recipe(PythonRecipe): version = '1.0.2' url = 'https://github.com/tiran/pysha3/archive/{version}.tar.gz' @@ -10,11 +9,8 @@ class Pysha3Recipe(PythonRecipe): def get_recipe_env(self, arch=None, with_flags_in_cc=True): env = super(Pysha3Recipe, self).get_recipe_env(arch, with_flags_in_cc) - # sets linker to use the correct gcc (cross compiler) - env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions' # CFLAGS may only be used to specify C compiler flags, for macro definitions use CPPFLAGS - env['CPPFLAGS'] = env['CFLAGS'] + ' -I{}/sources/python/3.5/include/python/'.format(self.ctx.ndk_dir) - env['CFLAGS'] = '' + env['CPPFLAGS'] = env['CFLAGS'] # LDFLAGS may only be used to specify linker flags, for libraries use LIBS env['LDFLAGS'] = env['LDFLAGS'].replace('-lm', '').replace('-lcrystax', '') env['LDFLAGS'] += ' -L{}'.format(os.path.join(self.ctx.bootstrap.build_dir, 'libs', arch.arch)) diff --git a/src/python-for-android/recipes/scrypt/__init__.py b/src/python-for-android/recipes/scrypt/__init__.py index 17a4ef5..0058639 100644 --- a/src/python-for-android/recipes/scrypt/__init__.py +++ b/src/python-for-android/recipes/scrypt/__init__.py @@ -29,9 +29,6 @@ def get_recipe_env(self, arch, with_flags_in_cc=True): ndk_dir_python = os.path.join(self.ctx.ndk_dir, 'sources/python/', python_version) env['LDFLAGS'] += ' -L{}'.format(os.path.join(ndk_dir_python, 'libs', arch.arch)) env['LDFLAGS'] += ' -lpython{}m'.format(python_version) - # until `pythonforandroid/archs.py` gets merged upstream: - # https://github.com/kivy/python-for-android/pull/1250/files#diff-569e13021e33ced8b54385f55b49cbe6 - env['CFLAGS'] += ' -I{}/include/python/'.format(ndk_dir_python) return env diff --git a/src/python-for-android/recipes/secp256k1/__init__.py b/src/python-for-android/recipes/secp256k1/__init__.py index d07cea4..5b74c86 100644 --- a/src/python-for-android/recipes/secp256k1/__init__.py +++ b/src/python-for-android/recipes/secp256k1/__init__.py @@ -19,8 +19,6 @@ class Secp256k1Recipe(PythonRecipe): def get_recipe_env(self, arch=None, with_flags_in_cc=True): env = super(Secp256k1Recipe, self).get_recipe_env(arch, with_flags_in_cc) - # sets linker to use the correct gcc (cross compiler) - env['LDSHARED'] = env['CC'] + ' -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions' libsecp256k1 = self.get_recipe('libsecp256k1', self.ctx) libsecp256k1_dir = libsecp256k1.get_build_dir(arch.arch) env['LDFLAGS'] += ' -L{}'.format(libsecp256k1_dir) @@ -32,12 +30,7 @@ def get_recipe_env(self, arch=None, with_flags_in_cc=True): ndk_dir_python = os.path.join(self.ctx.ndk_dir, 'sources/python/', python_version) env['LDFLAGS'] += ' -L{}'.format(os.path.join(ndk_dir_python, 'libs', arch.arch)) env['LDFLAGS'] += ' -lpython{}m'.format(python_version) - # until `pythonforandroid/archs.py` gets merged upstream: - # https://github.com/kivy/python-for-android/pull/1250/files#diff-569e13021e33ced8b54385f55b49cbe6 - env['CFLAGS'] += ' -I{}/include/python/'.format(ndk_dir_python) else: - env['PYTHON_ROOT'] = self.ctx.get_python_install_dir() - env['CFLAGS'] += ' -I' + env['PYTHON_ROOT'] + '/include/python{}'.format(python_version) env['LDFLAGS'] += " -lpython{}".format(python_version) env['LDFLAGS'] += " -lsecp256k1" return env From 9acaa605715a9ccdcad2da293cdc055292e60328 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Sun, 16 Sep 2018 22:20:52 +0200 Subject: [PATCH 05/10] Adds Sentry to roll pulling service, fixes #106 Reorganised code so both service and app can share helper functions. --- src/CHANGELOG.md | 2 +- src/etheroll/controller.py | 43 ++---------------------------- src/etheroll/utils.py | 54 ++++++++++++++++++++++++++++++++++++++ src/sentry_utils.py | 54 ++++++++++++++++++++++++++++++++++++++ src/service/main.py | 21 ++++++++++++--- 5 files changed, 129 insertions(+), 45 deletions(-) create mode 100644 src/sentry_utils.py diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md index 8aeb3b2..5df85e6 100644 --- a/src/CHANGELOG.md +++ b/src/CHANGELOG.md @@ -3,7 +3,7 @@ ## [Unreleased] - - Notify when roll processed, refs #57, #103 + - Notify when roll processed, refs #57, #103, #106 - Recipes LDSHARED & CFLAGS cleaning, refs #104 diff --git a/src/etheroll/controller.py b/src/etheroll/controller.py index 2f88190..1cdb44a 100755 --- a/src/etheroll/controller.py +++ b/src/etheroll/controller.py @@ -3,23 +3,21 @@ from kivy.app import App from kivy.clock import Clock, mainthread -from kivy.logger import LOG_LEVELS, Logger +from kivy.logger import Logger from kivy.properties import ObjectProperty from kivy.uix.floatlayout import FloatLayout from kivy.utils import platform from kivymd.bottomsheet import MDListBottomSheet from kivymd.theming import ThemeManager from raven import Client -from raven.conf import setup_logging -from raven.handlers.logging import SentryHandler from etheroll.constants import KEYSTORE_DIR_SUFFIX from etheroll.patches import patch_find_library_android, patch_typing_python351 from etheroll.settings import SettingsScreen from etheroll.switchaccount import SwitchAccountScreen from etheroll.utils import Dialog, load_kv_from_py, run_in_thread +from sentry_utils import configure_sentry from service.utils import start_service -from version import __version__ patch_find_library_android() patch_typing_python351() @@ -434,43 +432,6 @@ def captureException(self): raise -def configure_sentry(in_debug=False): - """ - Configure the Raven client, or create a dummy one if `in_debug` is `True`. - """ - key = 'b290ecc8934f4cb599e6fa6af6cc5cc2' - # the public DSN URL is not available on the Python client - # so we're exposing the secret and will be revoking it on abuse - # https://github.com/getsentry/raven-python/issues/569 - secret = '0ae02bcb5a75467d9b4431042bb98cb9' - project_id = '1111738' - dsn = 'https://{key}:{secret}@sentry.io/{project_id}'.format( - key=key, secret=secret, project_id=project_id) - if in_debug: - client = DebugRavenClient() - else: - client = Client(dsn=dsn, release=__version__) - # adds context for Android devices - if platform == 'android': - from jnius import autoclass - Build = autoclass("android.os.Build") - VERSION = autoclass('android.os.Build$VERSION') - android_os_build = { - 'model': Build.MODEL, - 'brand': Build.BRAND, - 'device': Build.DEVICE, - 'manufacturer': Build.MANUFACTURER, - 'version_release': VERSION.RELEASE, - } - client.user_context({'android_os_build': android_os_build}) - # Logger.error() to Sentry - # https://docs.sentry.io/clients/python/integrations/logging/ - handler = SentryHandler(client) - handler.setLevel(LOG_LEVELS.get('error')) - setup_logging(handler) - return client - - class EtherollApp(App): theme_cls = ThemeManager() diff --git a/src/etheroll/utils.py b/src/etheroll/utils.py index 4376761..1afb376 100644 --- a/src/etheroll/utils.py +++ b/src/etheroll/utils.py @@ -5,13 +5,20 @@ from kivy.app import App from kivy.clock import mainthread from kivy.lang import Builder +from kivy.logger import LOG_LEVELS from kivy.metrics import dp from kivy.uix.boxlayout import BoxLayout from kivy.uix.screenmanager import Screen +from kivy.utils import platform from kivymd.dialog import MDDialog from kivymd.label import MDLabel from kivymd.snackbar import Snackbar from layoutmargin import AddMargin, MarginLayout +from raven import Client +from raven.conf import setup_logging +from raven.handlers.logging import SentryHandler + +from version import __version__ def run_in_thread(fn): @@ -52,6 +59,53 @@ def load_kv_from_py(f): ) +def configure_sentry(in_debug=False): + """ + Configure the Raven client, or create a dummy one if `in_debug` is `True`. + """ + key = 'b290ecc8934f4cb599e6fa6af6cc5cc2' + # the public DSN URL is not available on the Python client + # so we're exposing the secret and will be revoking it on abuse + # https://github.com/getsentry/raven-python/issues/569 + secret = '0ae02bcb5a75467d9b4431042bb98cb9' + project_id = '1111738' + dsn = 'https://{key}:{secret}@sentry.io/{project_id}'.format( + key=key, secret=secret, project_id=project_id) + if in_debug: + client = DebugRavenClient() + else: + client = Client(dsn=dsn, release=__version__) + # adds context for Android devices + if platform == 'android': + from jnius import autoclass + Build = autoclass("android.os.Build") + VERSION = autoclass('android.os.Build$VERSION') + android_os_build = { + 'model': Build.MODEL, + 'brand': Build.BRAND, + 'device': Build.DEVICE, + 'manufacturer': Build.MANUFACTURER, + 'version_release': VERSION.RELEASE, + } + client.user_context({'android_os_build': android_os_build}) + # Logger.error() to Sentry + # https://docs.sentry.io/clients/python/integrations/logging/ + handler = SentryHandler(client) + handler.setLevel(LOG_LEVELS.get('error')) + setup_logging(handler) + return client + + +class DebugRavenClient(object): + """ + The DebugRavenClient should be used in debug mode, it just raises + the exception rather than capturing it. + """ + + def captureException(self): + raise + + class StringIOCBWrite(StringIO): """ Inherits StringIO, provides callback on write. diff --git a/src/sentry_utils.py b/src/sentry_utils.py new file mode 100644 index 0000000..1e7ad5c --- /dev/null +++ b/src/sentry_utils.py @@ -0,0 +1,54 @@ +from kivy.logger import LOG_LEVELS +from kivy.utils import platform +from raven import Client +from raven.conf import setup_logging +from raven.handlers.logging import SentryHandler + +from version import __version__ + + +def configure_sentry(in_debug=False): + """ + Configure the Raven client, or create a dummy one if `in_debug` is `True`. + """ + key = 'b290ecc8934f4cb599e6fa6af6cc5cc2' + # the public DSN URL is not available on the Python client + # so we're exposing the secret and will be revoking it on abuse + # https://github.com/getsentry/raven-python/issues/569 + secret = '0ae02bcb5a75467d9b4431042bb98cb9' + project_id = '1111738' + dsn = 'https://{key}:{secret}@sentry.io/{project_id}'.format( + key=key, secret=secret, project_id=project_id) + if in_debug: + client = DebugRavenClient() + else: + client = Client(dsn=dsn, release=__version__) + # adds context for Android devices + if platform == 'android': + from jnius import autoclass + Build = autoclass("android.os.Build") + VERSION = autoclass('android.os.Build$VERSION') + android_os_build = { + 'model': Build.MODEL, + 'brand': Build.BRAND, + 'device': Build.DEVICE, + 'manufacturer': Build.MANUFACTURER, + 'version_release': VERSION.RELEASE, + } + client.user_context({'android_os_build': android_os_build}) + # Logger.error() to Sentry + # https://docs.sentry.io/clients/python/integrations/logging/ + handler = SentryHandler(client) + handler.setLevel(LOG_LEVELS.get('error')) + setup_logging(handler) + return client + + +class DebugRavenClient(object): + """ + The DebugRavenClient should be used in debug mode, it just raises + the exception rather than capturing it. + """ + + def captureException(self): + raise diff --git a/src/service/main.py b/src/service/main.py index fda301a..0cee68b 100644 --- a/src/service/main.py +++ b/src/service/main.py @@ -1,15 +1,18 @@ import os from time import sleep, time +from kivy.logger import Logger from kivy.storage.jsonstore import JsonStore from kivy.utils import platform from plyer import notification +from raven import Client from ethereum_utils import AccountUtils from etheroll.constants import KEYSTORE_DIR_SUFFIX from etheroll.patches import patch_find_library_android from pyetheroll.constants import ROUND_DIGITS, ChainID from pyetheroll.etheroll import Etheroll +from sentry_utils import configure_sentry patch_find_library_android() PULL_FREQUENCY_SECONDS = 10 @@ -178,9 +181,21 @@ def do_notify(self, merged_logs): def main(): - service = MonitorRollsService() - service.set_auto_restart_service() - service.run() + # only send Android errors to Sentry + in_debug = platform != "android" + client = configure_sentry(in_debug) + try: + service = MonitorRollsService() + service.set_auto_restart_service() + service.run() + except Exception: + # avoid auto-restart loop + service.set_auto_restart_service(False) + if type(client) == Client: + Logger.info( + 'Errors will be sent to Sentry, run with "--debug" if you ' + 'are a developper and want to the error in the shell.') + client.captureException() if __name__ == '__main__': From c8a9ffdec29b1d6bbd431d9a7ceb61f91c62d227 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Mon, 17 Sep 2018 22:29:26 +0200 Subject: [PATCH 06/10] Switches to new Mainnet node, fixes #111 api.myetherapi.com seems down, migrate to infura.io --- src/pyetheroll/transaction_debugger.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/pyetheroll/transaction_debugger.py b/src/pyetheroll/transaction_debugger.py index 34b44e7..5e3a69c 100644 --- a/src/pyetheroll/transaction_debugger.py +++ b/src/pyetheroll/transaction_debugger.py @@ -38,8 +38,9 @@ def decode_contract_call(contract_abi: list, call_data: str): class HTTPProviderFactory: PROVIDER_URLS = { - ChainID.MAINNET: 'https://api.myetherapi.com/eth', - # also https://api.myetherapi.com/rop + # ChainID.MAINNET: 'https://api.myetherapi.com/eth', + ChainID.MAINNET: 'https://mainnet.infura.io', + # ChainID.ROPSTEN: 'https://api.myetherapi.com/rop', ChainID.ROPSTEN: 'https://ropsten.infura.io', } From fe1f3dbab887820eea82c08fd9d794ff9d664534 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Mon, 17 Sep 2018 23:27:40 +0200 Subject: [PATCH 07/10] Handles ConnectionError gracefully fixes #111 - handles ConnectionError with error dialog - UI test ConnectionError - improved UI tests reliability (thread joining) --- src/CHANGELOG.md | 1 + src/etheroll/controller.py | 5 ++- src/tests/ui/test_etheroll_ui.py | 72 ++++++++++++++++++++++++++++---- 3 files changed, 69 insertions(+), 9 deletions(-) diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md index 5df85e6..bd37dc1 100644 --- a/src/CHANGELOG.md +++ b/src/CHANGELOG.md @@ -5,6 +5,7 @@ - Notify when roll processed, refs #57, #103, #106 - Recipes LDSHARED & CFLAGS cleaning, refs #104 + - Updates broken Mainnet node, refs #111 ## [v20180911] diff --git a/src/etheroll/controller.py b/src/etheroll/controller.py index 1cdb44a..e3760c5 100755 --- a/src/etheroll/controller.py +++ b/src/etheroll/controller.py @@ -10,6 +10,7 @@ from kivymd.bottomsheet import MDListBottomSheet from kivymd.theming import ThemeManager from raven import Client +from requests.exceptions import ConnectionError from etheroll.constants import KEYSTORE_DIR_SUFFIX from etheroll.patches import patch_find_library_android, patch_typing_python351 @@ -308,7 +309,7 @@ def dialog_roll_error(self, exception): """ title = "Error rolling" body = str(exception) - if exception.args[0] == 'MAC mismatch': + if body == 'MAC mismatch': title = "Wrong password" body = "Can't unlock wallet, wrong password." account = self.current_account @@ -330,7 +331,7 @@ def player_roll_dice( roll_screen.toggle_widgets(False) tx_hash = self.pyetheroll.player_roll_dice( bet_size, chances, wallet_path, password, gas_price) - except ValueError as exception: + except (ValueError, ConnectionError) as exception: roll_screen.toggle_widgets(True) self.dialog_roll_error(exception) return diff --git a/src/tests/ui/test_etheroll_ui.py b/src/tests/ui/test_etheroll_ui.py index 31a33b9..aae6661 100644 --- a/src/tests/ui/test_etheroll_ui.py +++ b/src/tests/ui/test_etheroll_ui.py @@ -9,6 +9,7 @@ from hexbytes import HexBytes from kivy.clock import Clock +from requests.exceptions import ConnectionError from etheroll.controller import EtherollApp from etheroll.utils import Dialog @@ -28,6 +29,10 @@ def setUp(self): def tearDown(self): shutil.rmtree(self.temp_path, ignore_errors=True) + def helper_setup(self, app): + pass + # etheroll.SCREEN_SWITCH_DELAY = 0.001 + # sleep function that catches `dt` from Clock def pause(*args): time.sleep(0.000001) @@ -54,9 +59,14 @@ def advance_frames_for_drawer(self): """ self.advance_frames_for_screen() - def helper_setup(self, app): - pass - # etheroll.SCREEN_SWITCH_DELAY = 0.001 + def join_threads(self): + """ + Joins pending threads. + """ + threads = threading.enumerate() + # lists all threads but the main one + for thread in threads[1:]: + thread.join() def helper_test_empty_account(self, app): """ @@ -314,8 +324,7 @@ def helper_test_create_account_form(self, app): def helper_test_chances_input_binding(self, app): """ - Makes sure the chances input works as expected, refs: - https://github.com/AndreMiras/EtherollApp/issues/46 + Makes sure the chances input works as expected, refs #46. """ controller = app.root # retrieves both widgets from roll screen @@ -421,6 +430,8 @@ def helper_test_roll_history_no_account(self, app): # we should get redirected to the overview page self.advance_frames_for_screen() self.assertEqual(controller.screen_manager.current, 'roll_screen') + # e.g. fetch_update_balance thread + self.join_threads() def helper_test_roll(self, app): """ @@ -480,13 +491,59 @@ def helper_test_roll(self, app): screen_manager = controller.screen_manager screen_manager.current = 'roll_screen' self.advance_frames_for_screen() + # e.g. fetch_update_balance thread + self.join_threads() + + def helper_test_roll_connection_error(self, app): + """ + Makes sure ConnectionError on roll is handled gracefully, refs #111. + """ + controller = app.root + # retrieving the roll button, to click it + roll_screen = controller.roll_screen + roll_button = roll_screen.ids.roll_button_id + self.assertEqual(roll_button.text, 'Roll') + with mock.patch('web3.eth.Eth.getTransactionCount') \ + as m_getTransactionCount: + m_getTransactionCount.side_effect = \ + ConnectionError('Whatever ConnectionError') + roll_button.dispatch('on_release') + threads = threading.enumerate() + # since we may run into race condition with threading.enumerate() + # we make the test conditional + if len(threads) == 2: + # rolls should be pulled from a thread + self.assertEqual(len(threads), 2) + player_roll_dice_thread = threads[1] + self.assertEqual( + type(player_roll_dice_thread), threading.Thread) + self.assertTrue( + 'function Controller.player_roll_dice' + in str(player_roll_dice_thread._target)) + # waits for the end of the thread + player_roll_dice_thread.join() + self.advance_frames_for_screen() + self.assertEqual(len(threading.enumerate()), 1) + main_thread = threading.enumerate()[0] + self.assertEqual(type(main_thread), threading._MainThread) + # an error dialog should pop + dialogs = Dialog.dialogs + self.assertEqual(len(dialogs), 1) + dialog = dialogs[0] + self.assertEqual(dialog.title, 'Error rolling') + self.assertEqual(dialog.content.text, 'Whatever ConnectionError') + dialog.dismiss() + self.assertEqual(len(dialogs), 0) + # loads back the default screen + screen_manager = controller.screen_manager + screen_manager.current = 'roll_screen' + self.advance_frames_for_screen() def helper_test_roll_password(self, app): """ Makes sure wrong passwords are handled properly. Relevant error messages should prompt and it should be possible to - try again, refs: - https://github.com/AndreMiras/EtherollApp/issues/9 + try again, refs #9. """ controller = app.root # makes sure an account is selected @@ -562,6 +619,7 @@ def run_test(self, app, *args): self.helper_test_roll_history_no_tx(app) self.helper_test_roll_history_no_account(app) self.helper_test_roll(app) + self.helper_test_roll_connection_error(app) self.helper_test_roll_password(app) # Comment out if you are editing the test, it'll leave the # Window opened. From 78160f010d097319fe4d30d22f68b6c593255eb2 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Tue, 18 Sep 2018 00:19:08 +0200 Subject: [PATCH 08/10] Updates to kivy==1.10.1, fixes #100 - migrates to kivy==1.10.1 - migrates to kivymd personal fork --- buildozer.spec | 4 ++-- requirements.txt | 10 ++-------- src/CHANGELOG.md | 1 + 3 files changed, 5 insertions(+), 10 deletions(-) diff --git a/buildozer.spec b/buildozer.spec index 242f437..2b127ac 100644 --- a/buildozer.spec +++ b/buildozer.spec @@ -41,12 +41,12 @@ requirements = hostpython3crystax==3.6, python3crystax==3.6, setuptools, - kivy==d8ef8c2, + kivy==1.10.1, plyer==1.3.0, android, gevent, cffi, - https://gitlab.com/kivymd/KivyMD/repository/archive.zip?ref=19e587e6, + https://github.com/AndreMiras/KivyMD/archive/9b2206a.tar.gz, openssl, pyelliptic==1.5.7, asn1crypto==0.24.0, diff --git a/requirements.txt b/requirements.txt index 4a73dc8..e031a63 100644 --- a/requirements.txt +++ b/requirements.txt @@ -3,14 +3,8 @@ eth-testrpc==1.3.3 https://github.com/corpetty/py-etherscan-api/archive/cb91fb3.zip#egg=py-etherscan-api flake8 isort -# on Ubuntu 16.04 Xenial, the error: -# x11 - ImportError: No module named window_x11 -# is fixed by installing kivy master -# https://github.com/kivy/kivy/archive/27e3b90eae2a0155b22a435f1b6f65c913519db6.zip#egg=kivy -# on Ubuntu 18.04 Bionic, the compile error related to `MIX_INIT_MOD` is fixed by installing: -https://github.com/kivy/kivy/archive/d8ef8c2.zip#egg=kivy -# Kivy==1.10.0 -https://gitlab.com/kivymd/KivyMD/repository/archive.zip?ref=19e587e6#egg=kivymd +Kivy==1.10.1 +https://github.com/AndreMiras/KivyMD/archive/9b2206a.tar.gz#egg=kivymd ethereum==2.1.1 # 2017/10/30 develop https://github.com/ethereum/pyethapp/archive/8406f32.zip#egg=pyethapp diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md index bd37dc1..4b61ed9 100644 --- a/src/CHANGELOG.md +++ b/src/CHANGELOG.md @@ -6,6 +6,7 @@ - Notify when roll processed, refs #57, #103, #106 - Recipes LDSHARED & CFLAGS cleaning, refs #104 - Updates broken Mainnet node, refs #111 + - Upgrades to Kivy==1.10.1, refs #100 ## [v20180911] From 803a5e45dafb5746ceedcae703157b063078f906 Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Tue, 18 Sep 2018 00:28:17 +0200 Subject: [PATCH 09/10] Release docs update --- docs/Release.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/Release.md b/docs/Release.md index 31917fe..ff12071 100644 --- a/docs/Release.md +++ b/docs/Release.md @@ -12,6 +12,7 @@ git flow release start vYYYYMMDD ``` Now update the CHANGELOG.md `[Unreleased]` section to match the new release version. Also update the `__version__` string of the [version.py](/src/version.py) file. Then commit and finish release. +Optionally already update the direct download link from the [README.md](README.md). ``` git commit -a -m "vYYYYMMDD" git flow release finish From f69809d1e580a7ba9f93c07361293c7f300e676f Mon Sep 17 00:00:00 2001 From: Andre Miras Date: Tue, 18 Sep 2018 00:33:49 +0200 Subject: [PATCH 10/10] v20180918 --- README.md | 2 +- docs/Release.md | 4 ++-- src/CHANGELOG.md | 2 +- src/version.py | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 72136f0..76aff25 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Build Status](https://secure.travis-ci.org/AndreMiras/EtherollApp.png?branch=develop)](http://travis-ci.org/AndreMiras/EtherollApp) - + Provably fair dice game running on the [Ethereum blockchain](https://etheroll.com/#/smart-contract). Built with Python, [Kivy](https://github.com/kivy/kivy) and love. diff --git a/docs/Release.md b/docs/Release.md index ff12071..bf0bbc9 100644 --- a/docs/Release.md +++ b/docs/Release.md @@ -10,9 +10,9 @@ Start the release with git flow: ``` git flow release start vYYYYMMDD ``` -Now update the CHANGELOG.md `[Unreleased]` section to match the new release version. +Now update the [CHANGELOG.md](/src/CHANGELOG.md) `[Unreleased]` section to match the new release version. Also update the `__version__` string of the [version.py](/src/version.py) file. Then commit and finish release. -Optionally already update the direct download link from the [README.md](README.md). +Optionally already update the direct download link from the [README.md](/README.md). ``` git commit -a -m "vYYYYMMDD" git flow release finish diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md index 4b61ed9..118cfe8 100644 --- a/src/CHANGELOG.md +++ b/src/CHANGELOG.md @@ -1,7 +1,7 @@ # Change Log -## [Unreleased] +## [v20180918] - Notify when roll processed, refs #57, #103, #106 - Recipes LDSHARED & CFLAGS cleaning, refs #104 diff --git a/src/version.py b/src/version.py index 32c77d0..2f5c398 100644 --- a/src/version.py +++ b/src/version.py @@ -1 +1 @@ -__version__ = '2018.0911' +__version__ = '2018.0918'