Skip to content
This repository has been archived by the owner on Sep 1, 2021. It is now read-only.

Commit

Permalink
Bumps to pyetheroll==20191108
Browse files Browse the repository at this point in the history
Improves API key management and fixes user agent ban on Ropsten.
- fixes #156
- fixes #157
  • Loading branch information
AndreMiras committed Nov 8, 2019
1 parent 48fe4f8 commit 5cef327
Show file tree
Hide file tree
Showing 10 changed files with 101 additions and 16 deletions.
2 changes: 1 addition & 1 deletion buildozer.spec
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ requirements =
Pillow==5.2.0,
plyer==1.3.1,
pycryptodome==3.4.6,
pyetheroll==20191018,
pyetheroll==20191108,
Pygments==2.2.0,
python3==3.7.1,
pyzbar==0.1.8,
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ mypy==0.730
oscpy==0.3.0
Pillow==5.2.0
plyer==1.3.1
pyetheroll==20191018
pyetheroll==20191108
pytest
pyzbar==0.1.8
qrcode==6.0
Expand Down
8 changes: 7 additions & 1 deletion src/etherollapp/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
# Change Log


## [Unreleased]

- Fix 403 errors on Ropsten, refs #156, #157
- Fix broken F-Droid build, refs #167


## [v2019.1101]

- Bumps Kivy related dependencies
- Bump Kivy related dependencies
- Build and CI improvements
- Transfer out feature, refs #80, #105
- Migrate to NDK 19b, refs #154
Expand Down
1 change: 1 addition & 0 deletions src/etherollapp/api_key.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{ "key" : "E9K4A1AC8H1V3ZIR1DAIKZ6B961CRXF2DR" }
8 changes: 3 additions & 5 deletions src/etherollapp/etheroll/controller.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from etherollapp.etheroll.settings_screen import SettingsScreen
from etherollapp.etheroll.switchaccount import SwitchAccountScreen
from etherollapp.etheroll.ui_utils import Dialog, load_kv_from_py
from etherollapp.etheroll.utils import run_in_thread
from etherollapp.etheroll.utils import get_etherscan_api_key, run_in_thread
from etherollapp.osc.osc_app_server import OscAppServer
from etherollapp.sentry_utils import configure_sentry
from etherollapp.service.utils import start_roll_polling_service
Expand All @@ -38,7 +38,6 @@ def __init__(self, **kwargs):
self.disabled = True
Clock.schedule_once(self._after_init)
self._account_passwords = {}
self._pyetheroll = None

def _after_init(self, dt):
"""Inits pyethapp and binds events."""
Expand Down Expand Up @@ -82,9 +81,8 @@ def pyetheroll(self):
"""
from pyetheroll.etheroll import Etheroll
chain_id = Settings.get_stored_network()
if self._pyetheroll is None or self._pyetheroll.chain_id != chain_id:
self._pyetheroll = Etheroll(API_KEY_PATH, chain_id)
return self._pyetheroll
api_key = get_etherscan_api_key(API_KEY_PATH)
return Etheroll.get_or_create(api_key, chain_id)

@property
def account_utils(self):
Expand Down
36 changes: 36 additions & 0 deletions src/etherollapp/etheroll/utils.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import json
import logging
import os
import threading
from io import StringIO

from kivy.utils import platform

logger = logging.getLogger(__name__)


def run_in_thread(fn):
"""
Expand Down Expand Up @@ -48,6 +53,37 @@ def check_request_write_permission():
return had_permission


def get_etherscan_api_key(api_key_path: str = None) -> str:
"""
Tries to retrieve etherscan API key from path or from environment.
The files content should be in the form:
```json
{ "key" : "YourApiKeyToken" }
```
"""
DEFAULT_API_KEY_TOKEN = "YourApiKeyToken"
etherscan_api_key = os.environ.get("ETHERSCAN_API_KEY")
if etherscan_api_key is not None:
return etherscan_api_key
elif api_key_path is None:
logger.warning(
"Cannot get Etherscan API key. "
f"No path provided, defaulting to {DEFAULT_API_KEY_TOKEN}."
)
return DEFAULT_API_KEY_TOKEN
else:
try:
with open(api_key_path, mode="r") as key_file:
etherscan_api_key = json.loads(key_file.read())["key"]
except FileNotFoundError:
logger.warning(
f"Cannot get Etherscan API key. File {api_key_path} not found,"
f" defaulting to {DEFAULT_API_KEY_TOKEN}."
)
return DEFAULT_API_KEY_TOKEN
return etherscan_api_key


class StringIOCBWrite(StringIO):
"""Inherits StringIO, provides callback on write."""

Expand Down
8 changes: 3 additions & 5 deletions src/etherollapp/service/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
from etherollapp.ethereum_utils import AccountUtils
from etherollapp.etheroll.constants import API_KEY_PATH
from etherollapp.etheroll.settings import Settings
from etherollapp.etheroll.utils import get_etherscan_api_key
from etherollapp.osc.osc_app_client import OscAppClient
from etherollapp.sentry_utils import configure_sentry

Expand Down Expand Up @@ -71,7 +72,6 @@ def __init__(self, osc_server_port=None):
"""
Set `osc_server_port` to enable UI synchronization with service.
"""
self._pyetheroll = None
self._account_utils = None
# per address cached merged logs, used to compare with next pulls
self.merged_logs = {}
Expand Down Expand Up @@ -122,10 +122,8 @@ def pyetheroll(self):
Also recreates the object if the chain_id changed.
"""
chain_id = Settings.get_stored_network()
print(f'chain_id: {chain_id}')
if self._pyetheroll is None or self._pyetheroll.chain_id != chain_id:
self._pyetheroll = Etheroll(API_KEY_PATH, chain_id)
return self._pyetheroll
api_key = get_etherscan_api_key(API_KEY_PATH)
return Etheroll.get_or_create(api_key, chain_id)

def pull_account_rolls(self, account):
"""
Expand Down
Empty file.
45 changes: 45 additions & 0 deletions src/etherollapp/tests/etheroll/test_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from unittest import mock

from etherollapp.etheroll.utils import get_etherscan_api_key


class TestUtils:
def test_get_etherscan_api_key(self):
"""
Verifies the key can be retrieved from either:
1) environment
2) file
3) or fallbacks on default key
"""
expected_key = "0102030405060708091011121314151617"
# 1) environment
with mock.patch.dict(
"os.environ", {"ETHERSCAN_API_KEY": expected_key}
):
actual_key = get_etherscan_api_key()
assert actual_key == expected_key
# 2) file
read_data = '{ "key" : "%s" }' % (expected_key)
api_key_path = "api_key.json"
with mock.patch(
"builtins.open", mock.mock_open(read_data=read_data)
) as m_open:
actual_key = get_etherscan_api_key(api_key_path=api_key_path)
assert expected_key == actual_key
# verifies the file was read
assert m_open.call_args_list == [mock.call(api_key_path, mode="r")]
# 3) or fallbacks on default key
with mock.patch("builtins.open") as m_open, mock.patch(
"etherollapp.etheroll.utils.logger"
) as m_logger:
m_open.side_effect = FileNotFoundError
actual_key = get_etherscan_api_key(api_key_path)
assert "YourApiKeyToken" == actual_key
# verifies the fallback warning was logged
assert m_logger.warning.call_args_list == [
mock.call(
"Cannot get Etherscan API key. "
"File api_key.json not found, "
"defaulting to YourApiKeyToken."
)
]
7 changes: 4 additions & 3 deletions src/etherollapp/tests/service/test_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from kivy.app import App
from pyetheroll.constants import ChainID
from pyetheroll.etheroll import Etheroll

from etherollapp.service.main import EtherollApp, MonitorRollsService

Expand Down Expand Up @@ -119,19 +120,19 @@ def test_pyetheroll(self):
The cached value should be updated on (testnet/mainnet) network change.
"""
service = MonitorRollsService()
assert service._pyetheroll is None
# deletes the cached property eventually initialized by other tests
Etheroll._etheroll = None
with tempfile.TemporaryDirectory() as temp_path, \
patch_get_abi() as m_get_abi, \
mock.patch.dict('os.environ', {'XDG_CONFIG_HOME': temp_path}):
assert service.pyetheroll is not None
assert m_get_abi.mock_calls == [mock.call()]
assert service._pyetheroll is not None
pyetheroll = service.pyetheroll
# it's obviously pointing to the same object for now,
# but shouldn't not be later after we update some settings
assert pyetheroll == service.pyetheroll
assert pyetheroll.chain_id == ChainID.MAINNET
# the cached pyetheroll object is invalidated if the network changes
# the cached object is invalidated if the network changes
with mock.patch(
'etherollapp.service.main.Settings.get_stored_network'
) as m_get_stored_network, patch_get_abi() as m_get_abi:
Expand Down

0 comments on commit 5cef327

Please sign in to comment.