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

Commit

Permalink
Merge branch 'feature/ticket13_account_creation' into develop
Browse files Browse the repository at this point in the history
fixes #13
  • Loading branch information
AndreMiras committed Apr 13, 2018
2 parents 52f1743 + 2fdf3e7 commit 9f2a85f
Show file tree
Hide file tree
Showing 8 changed files with 544 additions and 25 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## [Unreleased]

- UI testing, refs #10
- Account creation, refs #13
- Setup continuous integration, refs #18
- Dockerized project, refs #19
- Caching Docker Linux build on Travis, refs #40
Expand Down
105 changes: 105 additions & 0 deletions src/ethereum_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import os
import shutil

import eth_keyfile
from ethereum.tools import keys
from ethereum.utils import is_string, to_string
from pyethapp.accounts import Account
from web3.auto import w3


class AccountUtils():

def __init__(self, keystore_dir):
# must be imported after `patch_find_library_android()`
from devp2p.app import BaseApp
from pyethapp.accounts import AccountsService
self.keystore_dir = keystore_dir
self.app = BaseApp(
config=dict(accounts=dict(keystore_dir=self.keystore_dir)))
AccountsService.register_with_app(self.app)
self.patch_ethereum_tools_keys()

def get_account_list(self):
"""
Returns the Account list.
"""
accounts_service = self.app.services.accounts
return accounts_service.accounts

@staticmethod
def get_private_key(wallet_path, wallet_password):
"""
Given wallet path and password, returns private key.
Made this way to workaround pyethapp slow account management:
https://github.com/ethereum/pyethapp/issues/292
"""
encrypted_key = open(wallet_path).read()
private_key = w3.eth.account.decrypt(encrypted_key, wallet_password)
return private_key

def new_account(self, password):
"""
Creates an account on the disk and returns it.
"""
account = Account.new(password, uuid=None)
account.path = os.path.join(
self.app.services.accounts.keystore_dir,
account.address.hex())
self.app.services.accounts.add_account(account)
return account

@staticmethod
def patch_ethereum_tools_keys():
"""
Patches `make_keystore_json()` to use `create_keyfile_json()`, see:
https://github.com/ethereum/pyethapp/issues/292
"""
keys.make_keystore_json = eth_keyfile.create_keyfile_json

def decode_keyfile_json(raw_keyfile_json, password):
if not is_string(password):
password = to_string(password)
return eth_keyfile.decode_keyfile_json(raw_keyfile_json, password)
keys.decode_keystore_json = decode_keyfile_json

@staticmethod
def deleted_account_dir(keystore_dir):
"""
Given a `keystore_dir`, returns the corresponding
`deleted_keystore_dir`.
>>> keystore_dir = '/tmp/keystore'
>>> AccountUtils.deleted_account_dir(keystore_dir)
u'/tmp/keystore-deleted'
>>> keystore_dir = '/tmp/keystore/'
>>> AccountUtils.deleted_account_dir(keystore_dir)
u'/tmp/keystore-deleted'
"""
keystore_dir = keystore_dir.rstrip('/')
keystore_dir_name = os.path.basename(keystore_dir)
deleted_keystore_dir_name = "%s-deleted" % (keystore_dir_name)
deleted_keystore_dir = os.path.join(
os.path.dirname(keystore_dir),
deleted_keystore_dir_name)
return deleted_keystore_dir

def delete_account(self, account):
"""
Deletes the given `account` from the `keystore_dir` directory.
Then deletes it from the `AccountsService` account manager instance.
In fact, moves it to another location; another directory at the same
level.
"""
keystore_dir = self.app.services.accounts.keystore_dir
deleted_keystore_dir = self.deleted_account_dir(keystore_dir)
# create the deleted account dir if required
if not os.path.exists(deleted_keystore_dir):
os.makedirs(deleted_keystore_dir)
# "removes" it from the file system
account_filename = os.path.basename(account.path)
deleted_account_path = os.path.join(
deleted_keystore_dir, account_filename)
shutil.move(account.path, deleted_account_path)
# deletes it from the `AccountsService` account manager instance
accounts_service = self.app.services.accounts
accounts_service.accounts.remove(account)
34 changes: 32 additions & 2 deletions src/etheroll.kv
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,35 @@
PushUp:


<CreateNewAccount>:
orientation: 'vertical'
MDTextField:
id: new_password1_id
hint_text: "Password"
helper_text: "Enter the password for encrypting your new account"
helper_text_mode: "on_focus"
password: True
write_tab: False
text: root.new_password1
on_text: root.new_password1 = args[1]
MDTextField:
id: new_password2_id
# TODO: do validation as we type
hint_text: "Password (again)"
helper_text: "Retype your password"
helper_text_mode: "on_focus"
password: True
write_tab: False
text: root.new_password2
on_text: root.new_password2 = args[1]
AnchorLayout:
MDRaisedButton:
id: create_account_button_id
text: "Create account"
on_release: root.create_account()
PushUp:


<SwitchAccountScreen>:
name: 'switch_account'
on_pre_enter:
Expand All @@ -59,11 +88,12 @@
SwitchAccount:
id: switch_account_id
MDBottomNavigationItem:
id: create_new_account_nav_item_id
name: "create"
text: "Create"
icon: "plus"
MDLabel:
text: "Not implemented"
CreateNewAccount:
id: create_new_account_id
MDBottomNavigationItem:
name: "import"
text: "Import"
Expand Down
126 changes: 118 additions & 8 deletions src/etheroll.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
from raven.conf import setup_logging
from raven.handlers.logging import SentryHandler

from ethereum_utils import AccountUtils
from utils import Dialog, patch_find_library_android, patch_typing_python351
from version import __version__

Expand Down Expand Up @@ -70,7 +71,7 @@ def load_account_list(self):
self.controller = App.get_running_app().root
account_list_id = self.ids.account_list_id
account_list_id.clear_widgets()
accounts = self.controller.pyethapp.services.accounts
accounts = self.controller.account_utils.get_account_list()
if len(accounts) == 0:
self.on_empty_account_list()
for account in accounts:
Expand All @@ -80,13 +81,123 @@ def load_account_list(self):
@staticmethod
def on_empty_account_list():
controller = App.get_running_app().root
keystore_dir = controller.pyethapp.services.accounts.keystore_dir
keystore_dir = controller.account_utils.keystore_dir
title = "No account found"
body = "No account found in:\n%s" % keystore_dir
dialog = Dialog.create_dialog(title, body)
dialog.open()


class CreateNewAccount(BoxLayout):
"""
Makes it possible to create json keyfiles.
"""

new_password1 = StringProperty()
new_password2 = StringProperty()

def __init__(self, **kwargs):
super(CreateNewAccount, self).__init__(**kwargs)
Clock.schedule_once(lambda dt: self.setup())

def setup(self):
"""
Sets security vs speed default values.
Plus hides the advanced widgets.
"""
self.controller = App.get_running_app().root

def verify_password_field(self):
"""
Makes sure passwords are matching and are not void.
"""
passwords_matching = self.new_password1 == self.new_password2
passwords_not_void = self.new_password1 != ''
return passwords_matching and passwords_not_void

def verify_fields(self):
"""
Verifies password fields are valid.
"""
return self.verify_password_field()

@staticmethod
def try_unlock(account, password):
"""
Just as a security measure, verifies we can unlock
the newly created account with provided password.
"""
# making sure it's locked first
account.lock()
try:
account.unlock(password)
except ValueError:
title = "Unlock error"
body = ""
body += "Couldn't unlock your account.\n"
body += "The issue should be reported."
dialog = Dialog.create_dialog(title, body)
dialog.open()
return

# @mainthread
def on_account_created(self, account):
"""
Switches to the newly created account.
Clears the form.
"""
self.controller.switch_account_screen.current_account = account
self.new_password1 = ''
self.new_password2 = ''

# @mainthread
def toggle_widgets(self, enabled):
"""
Enables/disables account creation widgets.
"""
self.disabled = not enabled

def show_redirect_dialog(self):
title = "Account created, redirecting..."
body = ""
body += "Your account was created, "
body += "you will be redirected to the overview."
dialog = Dialog.create_dialog(title, body)
dialog.open()

def load_landing_page(self):
"""
Returns to the landing page.
"""
screen_manager = self.controller.screen_manager
screen_manager.transition.direction = 'right'
screen_manager.current = 'roll_screen'

# @run_in_thread
def create_account(self):
"""
Creates an account from provided form.
Verify we can unlock it.
Disables widgets during the process, so the user doesn't try
to create another account during the process.
"""
self.toggle_widgets(False)
if not self.verify_fields():
Dialog.show_invalid_form_dialog()
self.toggle_widgets(True)
return
password = self.new_password1
Dialog.snackbar_message("Creating account...")
account = self.controller.account_utils.new_account(password=password)
Dialog.snackbar_message("Created!")
self.toggle_widgets(True)
self.on_account_created(account)
CreateNewAccount.try_unlock(account, password)
self.show_redirect_dialog()
self.load_landing_page()
return account


class CustomToolbar(Toolbar):
"""
Toolbar with helper method for loading default/back buttons.
Expand Down Expand Up @@ -334,12 +445,7 @@ def _after_init(self, dt):
def _init_pyethapp(self, keystore_dir=None):
if keystore_dir is None:
keystore_dir = self.get_keystore_path()
# must be imported after `patch_find_library_android()`
from devp2p.app import BaseApp
from pyethapp.accounts import AccountsService
self.pyethapp = BaseApp(
config=dict(accounts=dict(keystore_dir=keystore_dir)))
AccountsService.register_with_app(self.pyethapp)
self.account_utils = AccountUtils(keystore_dir=keystore_dir)

@property
def pyetheroll(self):
Expand Down Expand Up @@ -440,6 +546,10 @@ def update_profit_property(self):
def navigation(self):
return self.ids.navigation_id

@property
def screen_manager(self):
return self.ids.screen_manager_id

@property
def roll_screen(self):
return self.ids.roll_screen_id
Expand Down
10 changes: 6 additions & 4 deletions src/pyetheroll.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
from web3.auto import w3
from web3.contract import Contract

from ethereum_utils import AccountUtils

requests_cache_params = {
'cache_name': 'requests_cache',
'backend': 'sqlite',
Expand Down Expand Up @@ -314,8 +316,8 @@ def play_with_contract():
def player_roll_dice(
self, bet_size_ether, chances, wallet_path, wallet_password):
"""
Work in progress:
https://github.com/AndreMiras/EtherollApp/issues/1
Signs and broadcasts `playerRollDice` transaction.
Returns transaction hash.
"""
roll_under = chances
value_wei = w3.toWei(bet_size_ether, 'ether')
Expand All @@ -335,8 +337,8 @@ def player_roll_dice(
}
transaction = self.contract.functions.playerRollDice(
roll_under).buildTransaction(transaction)
encrypted_key = open(wallet_path).read()
private_key = w3.eth.account.decrypt(encrypted_key, wallet_password)
private_key = AccountUtils.get_private_key(
wallet_path, wallet_password)
signed_tx = self.web3.eth.account.signTransaction(
transaction, private_key)
tx_hash = self.web3.eth.sendRawTransaction(signed_tx.rawTransaction)
Expand Down
Loading

0 comments on commit 9f2a85f

Please sign in to comment.