mirror of
https://github.com/LBRYFoundation/LBRY-Vault.git
synced 2025-08-27 07:23:25 +00:00
Ultimate goal is to try and stop the daemon race at startup. Need to isolate logic of checking for server and creating one.
337 lines
12 KiB
Python
Executable file
337 lines
12 KiB
Python
Executable file
#!/usr/bin/env python2
|
|
# -*- mode: python -*-
|
|
#
|
|
# Electrum - lightweight Bitcoin client
|
|
# Copyright (C) 2011 thomasv@gitorious
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import os
|
|
import sys
|
|
|
|
|
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
|
is_bundle = getattr(sys, 'frozen', False)
|
|
is_local = not is_bundle and os.path.exists(os.path.join(script_dir, "setup-release.py"))
|
|
is_android = 'ANDROID_DATA' in os.environ
|
|
|
|
if is_local or is_android:
|
|
sys.path.insert(0, os.path.join(script_dir, 'packages'))
|
|
elif is_bundle and sys.platform=='darwin':
|
|
sys.path.insert(0, os.getcwd() + "/lib/python2.7/packages")
|
|
|
|
|
|
def check_imports():
|
|
# pure-python dependencies need to be imported here for pyinstaller
|
|
try:
|
|
import dns
|
|
import aes
|
|
import ecdsa
|
|
import requests
|
|
import six
|
|
import qrcode
|
|
import pbkdf2
|
|
import google.protobuf
|
|
except ImportError as e:
|
|
sys.exit("Error: %s. Try 'sudo pip install <module-name>'"%e.message)
|
|
# the following imports are for pyinstaller
|
|
from google.protobuf import descriptor
|
|
from google.protobuf import message
|
|
from google.protobuf import reflection
|
|
from google.protobuf import descriptor_pb2
|
|
# check that we have the correct version of ecdsa
|
|
try:
|
|
from ecdsa.ecdsa import curve_secp256k1, generator_secp256k1
|
|
except Exception:
|
|
sys.exit("cannot import ecdsa.curve_secp256k1. You probably need to upgrade ecdsa.\nTry: sudo pip install --upgrade ecdsa")
|
|
# make sure that certificates are here
|
|
assert os.path.exists(requests.utils.DEFAULT_CA_BUNDLE_PATH)
|
|
|
|
|
|
if not is_android:
|
|
check_imports()
|
|
|
|
# load local module as electrum
|
|
if is_bundle or is_local or is_android:
|
|
import imp
|
|
imp.load_module('electrum', *imp.find_module('lib'))
|
|
imp.load_module('electrum_gui', *imp.find_module('gui'))
|
|
|
|
|
|
from electrum import SimpleConfig, Network, Wallet, WalletStorage
|
|
from electrum.util import print_msg, print_stderr, json_encode, json_decode
|
|
from electrum.util import set_verbosity, InvalidPassword
|
|
from electrum.plugins import Plugins
|
|
from electrum.commands import get_parser, known_commands, Commands, config_variables
|
|
from electrum.daemon import Daemon
|
|
|
|
|
|
# get password routine
|
|
def prompt_password(prompt, confirm=True):
|
|
import getpass
|
|
password = getpass.getpass(prompt, stream=None)
|
|
if password and confirm:
|
|
password2 = getpass.getpass("Confirm: ")
|
|
if password != password2:
|
|
sys.exit("Error: Passwords do not match.")
|
|
if not password:
|
|
password = None
|
|
return password
|
|
|
|
|
|
|
|
def run_non_RPC(config):
|
|
cmdname = config.get('cmd')
|
|
|
|
storage = WalletStorage(config.get_wallet_path())
|
|
if storage.file_exists:
|
|
sys.exit("Error: Remove the existing wallet first!")
|
|
|
|
def password_dialog():
|
|
return prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
|
|
|
|
if cmdname == 'restore':
|
|
text = config.get('text')
|
|
password = password_dialog() if Wallet.is_seed(text) or Wallet.is_xprv(text) or Wallet.is_private_key(text) else None
|
|
try:
|
|
wallet = Wallet.from_text(text, password, storage)
|
|
except BaseException as e:
|
|
sys.exit(str(e))
|
|
wallet.create_main_account()
|
|
if not config.get('offline'):
|
|
network = Network(config)
|
|
network.start()
|
|
wallet.start_threads(network)
|
|
print_msg("Recovering wallet...")
|
|
wallet.synchronize()
|
|
wallet.wait_until_synchronized()
|
|
msg = "Recovery successful" if wallet.is_found() else "Found no history for this wallet"
|
|
else:
|
|
msg = "This wallet was restored offline. It may contain more addresses than displayed."
|
|
print_msg(msg)
|
|
|
|
elif cmdname == 'create':
|
|
password = password_dialog()
|
|
wallet = Wallet(storage)
|
|
seed = wallet.make_seed()
|
|
wallet.add_seed(seed, password)
|
|
wallet.create_master_keys(password)
|
|
wallet.create_main_account()
|
|
wallet.synchronize()
|
|
print_msg("Your wallet generation seed is:\n\"%s\"" % seed)
|
|
print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
|
|
|
|
elif cmdname == 'deseed':
|
|
if not wallet.seed:
|
|
print_msg("Error: This wallet has no seed")
|
|
else:
|
|
ns = wallet.storage.path + '.seedless'
|
|
print_msg("Warning: you are going to create a seedless wallet'\nIt will be saved in '%s'" % ns)
|
|
if raw_input("Are you sure you want to continue? (y/n) ") in ['y', 'Y', 'yes']:
|
|
wallet.storage.path = ns
|
|
wallet.seed = ''
|
|
wallet.storage.put('seed', '')
|
|
wallet.use_encryption = False
|
|
wallet.storage.put('use_encryption', wallet.use_encryption)
|
|
for k in wallet.imported_keys.keys():
|
|
wallet.imported_keys[k] = ''
|
|
wallet.storage.put('imported_keys', wallet.imported_keys)
|
|
print_msg("Done.")
|
|
else:
|
|
print_msg("Action canceled.")
|
|
wallet.storage.write()
|
|
sys.exit(0)
|
|
|
|
wallet.storage.write()
|
|
print_msg("Wallet saved in '%s'" % wallet.storage.path)
|
|
sys.exit(0)
|
|
|
|
|
|
def init_cmdline(config_options):
|
|
config = SimpleConfig(config_options)
|
|
cmdname = config.get('cmd')
|
|
cmd = known_commands[cmdname]
|
|
|
|
if cmdname == 'signtransaction' and config.get('privkey'):
|
|
cmd.requires_wallet = False
|
|
cmd.requires_password = False
|
|
|
|
if cmdname in ['payto', 'paytomany'] and config.get('unsigned'):
|
|
cmd.requires_password = False
|
|
|
|
if cmdname in ['payto', 'paytomany'] and config.get('broadcast'):
|
|
cmd.requires_network = True
|
|
|
|
if cmdname in ['createrawtx'] and config.get('unsigned'):
|
|
cmd.requires_password = False
|
|
cmd.requires_wallet = False
|
|
|
|
# instanciate wallet for command-line
|
|
storage = WalletStorage(config.get_wallet_path())
|
|
|
|
if cmd.requires_wallet and not storage.file_exists:
|
|
print_msg("Error: Wallet file not found.")
|
|
print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
|
|
sys.exit(0)
|
|
|
|
# important warning
|
|
if cmd.name in ['getprivatekeys']:
|
|
print_stderr("WARNING: ALL your private keys are secret.")
|
|
print_stderr("Exposing a single private key can compromise your entire wallet!")
|
|
print_stderr("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
|
|
|
|
# commands needing password
|
|
if cmd.requires_password and storage.get('use_encryption'):
|
|
if config.get('password'):
|
|
password = config.get('password')
|
|
else:
|
|
password = prompt_password('Password:', False)
|
|
if not password:
|
|
print_msg("Error: Password required")
|
|
sys.exit(1)
|
|
else:
|
|
password = None
|
|
|
|
config_options['password'] = password
|
|
|
|
if cmd.name == 'password':
|
|
new_password = prompt_password('New password:')
|
|
config_options['new_password'] = new_password
|
|
|
|
return cmd, password
|
|
|
|
|
|
def run_offline_command(config, config_options):
|
|
cmdname = config.get('cmd')
|
|
cmd = known_commands[cmdname]
|
|
storage = WalletStorage(config.get_wallet_path())
|
|
wallet = Wallet(storage) if cmd.requires_wallet else None
|
|
# check password
|
|
if cmd.requires_password and storage.get('use_encryption'):
|
|
password = config_options.get('password')
|
|
try:
|
|
seed = wallet.check_password(password)
|
|
except InvalidPassword:
|
|
print_msg("Error: This password does not decode this wallet.")
|
|
sys.exit(1)
|
|
if cmd.requires_network:
|
|
print_stderr("Warning: running command offline")
|
|
# arguments passed to function
|
|
args = map(lambda x: config.get(x), cmd.params)
|
|
# decode json arguments
|
|
args = map(json_decode, args)
|
|
# options
|
|
args += map(lambda x: config.get(x), cmd.options)
|
|
cmd_runner = Commands(config, wallet, None,
|
|
password=config_options.get('password'),
|
|
new_password=config_options.get('new_password'))
|
|
func = getattr(cmd_runner, cmd.name)
|
|
result = func(*args)
|
|
# save wallet
|
|
if wallet:
|
|
wallet.storage.write()
|
|
return result
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
# on osx, delete Process Serial Number arg generated for apps launched in Finder
|
|
sys.argv = filter(lambda x: not x.startswith('-psn'), sys.argv)
|
|
|
|
# old 'help' syntax
|
|
if len(sys.argv)>1 and sys.argv[1] == 'help':
|
|
sys.argv.remove('help')
|
|
sys.argv.append('-h')
|
|
|
|
# read arguments from stdin pipe and prompt
|
|
for i, arg in enumerate(sys.argv):
|
|
if arg == '-':
|
|
if not sys.stdin.isatty():
|
|
sys.argv[i] = sys.stdin.read()
|
|
break
|
|
else:
|
|
raise BaseException('Cannot get argument from stdin')
|
|
elif arg == '?':
|
|
sys.argv[i] = raw_input("Enter argument:")
|
|
elif arg == ':':
|
|
sys.argv[i] = prompt_password('Enter argument (will not echo):', False)
|
|
|
|
# parse command line
|
|
parser = get_parser()
|
|
args = parser.parse_args()
|
|
|
|
# config is an object passed to the various constructors (wallet, interface, gui)
|
|
if is_android:
|
|
config_options = {
|
|
'verbose': True,
|
|
'cmd': 'gui',
|
|
'gui': 'kivy',
|
|
#'auto_connect': True,
|
|
}
|
|
else:
|
|
config_options = args.__dict__
|
|
for k, v in config_options.items():
|
|
if v is None or (k in config_variables.get(args.cmd, {}).keys()):
|
|
config_options.pop(k)
|
|
if config_options.get('server'):
|
|
config_options['auto_connect'] = False
|
|
|
|
if config_options.get('portable'):
|
|
config_options['electrum_path'] = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'electrum_data')
|
|
|
|
set_verbosity(config_options.get('verbose'))
|
|
|
|
# check uri
|
|
uri = config_options.get('url')
|
|
if uri:
|
|
if not uri.startswith('bitcoin:'):
|
|
print_stderr('unknown command:', uri)
|
|
sys.exit(1)
|
|
config_options['url'] = uri
|
|
|
|
config = SimpleConfig(config_options)
|
|
cmdname = config.get('cmd')
|
|
|
|
# initialize plugins.
|
|
gui_name = config.get('gui', 'qt') if cmdname == 'gui' else 'cmdline'
|
|
plugins = Plugins(config, is_bundle or is_local or is_android, gui_name)
|
|
|
|
# run non-RPC commands separately
|
|
if cmdname in ['create', 'restore', 'deseed']:
|
|
run_non_RPC(config)
|
|
sys.exit(0)
|
|
|
|
if cmdname == 'gui':
|
|
result = Daemon.gui_command(config, config_options, plugins)
|
|
elif cmdname == 'daemon':
|
|
result = Daemon.daemon_command(config, config_options)
|
|
else:
|
|
# command line
|
|
init_cmdline(config_options)
|
|
run_offline, result = Daemon.cmdline_command(config, config_options)
|
|
if run_offline:
|
|
cmd = known_commands[cmdname]
|
|
if cmd.requires_network:
|
|
print_msg("Daemon not running; try 'electrum daemon start'")
|
|
sys.exit(1)
|
|
result = run_offline_command(config, config_options)
|
|
|
|
# print result
|
|
if type(result) in [str, unicode]:
|
|
print_msg(result)
|
|
elif type(result) is dict and result.get('error'):
|
|
print_stderr(result.get('error'))
|
|
elif result is not None:
|
|
print_msg(json_encode(result))
|
|
sys.exit(0)
|