move start_daemon code to main script
[electrum-nvc.git] / electrum
1 #!/usr/bin/env python
2 #
3 # Electrum - lightweight Bitcoin client
4 # Copyright (C) 2011 thomasv@gitorious
5 #
6 # This program is free software: you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation, either version 3 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 # GNU General Public License for more details.
15 #
16 # You should have received a copy of the GNU General Public License
17 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 from decimal import Decimal
20 import json
21 import optparse
22 import os
23 import re
24 import ast
25 import sys
26 import time
27 import traceback
28
29 try:
30     import ecdsa  # todo: 'ecdsa' imported but unused
31 except ImportError:
32     sys.exit("Error: python-ecdsa does not seem to be installed. Try 'sudo pip install ecdsa'")
33
34 try:
35     import aes  # todo: 'aes' imported but unused
36 except ImportError:
37     sys.exit("Error: AES does not seem to be installed. Try 'sudo pip install slowaes'")
38
39
40 is_local = os.path.dirname(os.path.realpath(__file__)) == os.getcwd()
41 is_android = 'ANDROID_DATA' in os.environ
42
43 import __builtin__
44 __builtin__.use_local_modules = is_local or is_android
45
46 # load local module as electrum
47 if __builtin__.use_local_modules:
48     import imp
49     imp.load_module('electrum', *imp.find_module('lib'))
50     imp.load_module('electrum_gui', *imp.find_module('gui'))
51
52 from electrum import *  # todo: import * is generally frowned upon. should import just what is used
53
54
55 # get password routine
56 def prompt_password(prompt, confirm=True):
57     import getpass
58     if sys.stdin.isatty():
59         password = getpass.getpass(prompt)
60         if password and confirm:
61             password2 = getpass.getpass("Confirm: ")
62             if password != password2:
63                 sys.exit("Error: Passwords do not match.")
64     else:
65         password = raw_input(prompt)
66     if not password:
67         password = None
68     return password
69
70
71 def arg_parser():
72     usage = "%prog [options] command"
73     parser = optparse.OptionParser(prog=usage, add_help_option=False)
74     parser.add_option("-h", "--help", action="callback", callback=print_help_cb, help="show this help text")
75     parser.add_option("-g", "--gui", dest="gui", help="User interface: qt, lite, gtk, text or stdio")
76     parser.add_option("-w", "--wallet", dest="wallet_path", help="wallet path (default: electrum.dat)")
77     parser.add_option("-o", "--offline", action="store_true", dest="offline", default=False, help="remain offline")
78     parser.add_option("-C", "--concealed", action="store_true", dest="concealed", default=False, help="don't echo seed to console when restoring")
79     parser.add_option("-a", "--all", action="store_true", dest="show_all", default=False, help="show all addresses")
80     parser.add_option("-l", "--labels", action="store_true", dest="show_labels", default=False, help="show the labels of listed addresses")
81     parser.add_option("-f", "--fee", dest="tx_fee", default=None, help="set tx fee")
82     parser.add_option("-F", "--fromaddr", dest="from_addr", default=None, help="set source address for payto/mktx. if it isn't in the wallet, it will ask for the private key unless supplied in the format public_key:private_key. It's not saved in the wallet.")
83     parser.add_option("-c", "--changeaddr", dest="change_addr", default=None, help="set the change address for payto/mktx. default is a spare address, or the source address if it's not in the wallet")
84     parser.add_option("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is either t (tcp), h (http), s (tcp+ssl), or g (https)")
85     parser.add_option("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port], where type is socks4,socks5 or http")
86     parser.add_option("-v", "--verbose", action="store_true", dest="verbose", default=False, help="show debugging information")
87     parser.add_option("-P", "--portable", action="store_true", dest="portable", default=False, help="portable wallet")
88     parser.add_option("-L", "--lang", dest="language", default=None, help="defaut language used in GUI")
89     parser.add_option("-u", "--usb", dest="bitkey", action="store_true", help="Turn on support for hardware wallets (EXPERIMENTAL)")
90     parser.add_option("-G", "--gap", dest="gap_limit", default=None, help="gap limit")
91     parser.add_option("-W", "--password", dest="password", default=None, help="set password for usage with commands (currently only implemented for create command, do not use it for longrunning gui session since the password is visible in /proc)")
92     parser.add_option("-1", "--oneserver", action="store_true", dest="oneserver", default=False, help="connect to one server only")
93     parser.add_option("--bip32", action="store_true", dest="bip32", default=False, help="bip32 (not final)")
94     parser.add_option("--mpk", dest="mpk", default=False, help="restore from master public key")
95     return parser
96
97
98 def print_help(parser):
99     parser.print_help()
100     print_msg("Type 'electrum help <command>' to see the help for a specific command")
101     print_msg("Type 'electrum --help' to see the list of options")
102     run_command(known_commands['help'])
103     sys.exit(1)
104
105
106 def print_help_cb(self, opt, value, parser):
107     print_help(parser)
108
109
110 def run_command(cmd, password=None, args=[]):
111     import socket
112     if cmd.requires_network and not options.offline:
113         daemon_started = False
114         while True:
115             try:
116                 network = NetworkProxy(config)
117                 break
118             except socket.error:
119                 if cmd != 'daemon':
120                     if not daemon_started:
121                         print "Starting daemon [%s]"%config.get('server')
122                         daemon_started = True
123                         pid = os.fork()
124                         if (pid == 0): # The first child.
125                             os.chdir("/")
126                             os.setsid()
127                             os.umask(0)
128                             pid2 = os.fork()
129                             if (pid2 == 0):  # Second child
130                                 server = NetworkServer(config)
131                                 try:
132                                     server.main_loop()
133                                 except KeyboardInterrupt:
134                                     print "Ctrl C - Stopping server"
135                                 sys.exit(1)
136                             sys.exit(0)
137                     else:
138                         time.sleep(0.1)
139                 else:
140                     print "Daemon not running"
141                     sys.exit(1)
142
143         network = network
144         network.start()
145         if wallet:
146             wallet.start_threads(network)
147             wallet.update()
148     else:
149         network = None
150
151     cmd_runner = Commands(wallet, network)
152     func = getattr(cmd_runner, cmd.name)
153     cmd_runner.password = password
154     try:
155         result = func(*args[1:])
156     except Exception:
157         print "ecxeption"
158         traceback.print_exc(file=sys.stdout)
159         sys.exit(1)
160
161
162     if cmd.requires_network and not options.offline:
163         if wallet:
164             wallet.stop_threads()
165
166
167     if type(result) == str:
168         util.print_msg(result)
169     elif result is not None:
170         util.print_json(result)
171
172
173
174
175
176
177 if __name__ == '__main__':
178
179     parser = arg_parser()
180     options, args = parser.parse_args()
181     if options.portable and options.wallet_path is None:
182         options.electrum_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'electrum_data')
183
184     # config is an object passed to the various constructors (wallet, interface, gui)
185     if is_android:
186         config_options = {
187             'portable': True,
188             'verbose': True,
189             'gui': 'android',
190             'auto_cycle': True,
191         }
192     else:
193         config_options = eval(str(options))
194         for k, v in config_options.items():
195             if v is None:
196                 config_options.pop(k)
197
198     set_verbosity(config_options.get('verbose'))
199
200     config = SimpleConfig(config_options)
201
202     if len(args) == 0:
203         url = None
204         cmd = 'gui'
205     elif len(args) == 1 and re.match('^bitcoin:', args[0]):
206         url = args[0]
207         cmd = 'gui'
208     else:
209         cmd = args[0]
210
211     if cmd == 'gui':
212         gui_name = config.get('gui', 'classic')
213         if gui_name in ['lite', 'classic']:
214             gui_name = 'qt'
215         try:
216             gui = __import__('electrum_gui.' + gui_name, fromlist=['electrum_gui'])
217         except ImportError:
218             traceback.print_exc(file=sys.stdout)
219             sys.exit()
220             #sys.exit("Error: Unknown GUI: " + gui_name )
221
222         # network interface
223         if not options.offline:
224             network = Network(config)
225             network.start()
226         else:
227             network = None
228
229         gui = gui.ElectrumGui(config, network)
230         gui.main(url)
231
232         if network:
233             network.stop()
234
235         # we use daemon threads, their termination is enforced.
236         # this sleep command gives them time to terminate cleanly.
237         time.sleep(0.1)
238         sys.exit(0)
239
240     if cmd not in known_commands:
241         cmd = 'help'
242
243     cmd = known_commands[cmd]
244
245     # instanciate wallet for command-line
246     storage = WalletStorage(config)
247
248
249     if cmd.name in ['create', 'restore']:
250         if storage.file_exists:
251             sys.exit("Error: Remove the existing wallet first!")
252         if options.password is not None:
253             password = options.password
254         elif cmd.name == 'restore' and options.mpk:
255             password = None
256         else:
257             password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
258
259         # if config.server is set, the user either passed the server on command line
260         # or chose it previously already. if he didn't pass a server on the command line,
261         # we just pick up a random one.
262         if not config.get('server'):
263             config.set_key('server', pick_random_server())
264
265         #fee = options.tx_fee if options.tx_fee else raw_input("fee (default:%s):" % (str(Decimal(wallet.fee)/100000000)))
266         #gap = options.gap_limit if options.gap_limit else raw_input("gap limit (default 5):")
267         #if fee:
268         #    wallet.set_fee(float(fee)*100000000)
269         #if gap:
270         #    wallet.change_gap_limit(int(gap))
271
272         if cmd.name == 'restore':
273             if options.mpk:
274                 wallet = Wallet.from_mpk(options.mpk, storage)
275             else:
276                 import getpass
277                 seed = getpass.getpass(prompt="seed:", stream=None) if options.concealed else raw_input("seed:")
278                 wallet = Wallet.from_seed(str(seed),storage)
279                 if not wallet:
280                     sys.exit("Error: Invalid seed")
281                 wallet.save_seed(password)
282
283             if not options.offline:
284                 network = Network(config)
285                 network.start()
286                 wallet.start_threads(network)
287                 print_msg("Recovering wallet...")
288                 wallet.restore(lambda x: x)
289                 if wallet.is_found():
290                     print_msg("Recovery successful")
291                 else:
292                     print_msg("Warning: Found no history for this wallet")
293             else:
294                 wallet.synchronize()
295                 print_msg("Warning: This wallet was restored offline. It may contain more addresses than displayed.")
296
297         else:
298             wallet = Wallet(storage)
299             wallet.init_seed(None)
300             wallet.save_seed(password)
301             wallet.synchronize()
302             print_msg("Your wallet generation seed is:\n\"%s\"" % wallet.get_mnemonic(password))
303             print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
304
305         print_msg("Wallet saved in '%s'" % wallet.storage.path)
306
307         # terminate
308         sys.exit(0)
309
310
311     if cmd.name not in ['create', 'restore'] and cmd.requires_wallet and not storage.file_exists:
312         print_msg("Error: Wallet file not found.")
313         print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
314         sys.exit(0)
315
316
317     if cmd.requires_wallet:
318         wallet = Wallet(storage)
319     else:
320         wallet = None
321
322
323     # important warning
324     if cmd.name in ['dumpprivkey', 'dumpprivkeys']:
325         print_msg("WARNING: ALL your private keys are secret.")
326         print_msg("Exposing a single private key can compromise your entire wallet!")
327         print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
328
329     # commands needing password
330     if cmd.requires_password:
331         if wallet.seed == '':
332             seed = ''
333             password = None
334         elif wallet.use_encryption:
335             password = prompt_password('Password:', False)
336             if not password:
337                 print_msg("Error: Password required")
338                 sys.exit(1)
339             # check password
340             try:
341                 seed = wallet.get_seed(password)
342             except Exception:
343                 print_msg("Error: This password does not decode this wallet.")
344                 sys.exit(1)
345         else:
346             password = None
347             seed = wallet.get_seed(None)
348     else:
349         password = None
350
351     # add missing arguments, do type conversions
352     if cmd.name == 'importprivkey':
353         # See if they specificed a key on the cmd line, if not prompt
354         if len(args) == 1:
355             args[1] = prompt_password('Enter PrivateKey (will not echo):', False)
356
357     elif cmd.name == 'signrawtransaction':
358         args = [cmd, args[1], json.loads(args[2]) if len(args) > 2 else [], json.loads(args[3]) if len(args) > 3 else []]
359
360     elif cmd.name == 'createmultisig':
361         args = [cmd, int(args[1]), json.loads(args[2])]
362
363     elif cmd.name == 'createrawtransaction':
364         args = [cmd, json.loads(args[1]), json.loads(args[2])]
365
366     elif cmd.name == 'listaddresses':
367         args = [cmd, options.show_all, options.show_labels]
368
369     elif cmd.name in ['payto', 'mktx']:
370         domain = [options.from_addr] if options.from_addr else None
371         args = ['mktx', args[1], Decimal(args[2]), Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain]
372
373     elif cmd.name in ['paytomany', 'mksendmanytx']:
374         domain = [options.from_addr] if options.from_addr else None
375         outputs = []
376         for i in range(1, len(args), 2):
377             if len(args) < i+2:
378                 print_msg("Error: Mismatched arguments.")
379                 sys.exit(1)
380             outputs.append((args[i], Decimal(args[i+1])))
381         args = ['mksendmanytx', outputs, Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain]
382
383     elif cmd.name == 'help':
384         if len(args) < 2:
385             print_help(parser)
386
387     # check the number of arguments
388     if len(args) - 1 < cmd.min_args:
389         print_msg("Not enough arguments")
390         print_msg("Syntax:", cmd.syntax)
391         sys.exit(1)
392
393     if cmd.max_args >= 0 and len(args) - 1 > cmd.max_args:
394         print_msg("too many arguments", args)
395         print_msg("Syntax:", cmd.syntax)
396         sys.exit(1)
397
398     if cmd.max_args < 0:
399         if len(args) > cmd.min_args + 1:
400             message = ' '.join(args[cmd.min_args:])
401             print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message))
402             args = args[0:cmd.min_args] + [message]
403
404
405
406     # run the command
407     if cmd.name == 'deseed':
408         if not wallet.seed:
409             print_msg("Error: This wallet has no seed")
410         else:
411             ns = wallet.storage.path + '.seedless'
412             print_msg("Warning: you are going to create a seedless wallet'\nIt will be saved in '%s'" % ns)
413             if raw_input("Are you sure you want to continue? (y/n) ") in ['y', 'Y', 'yes']:
414                 wallet.storage.path = ns
415                 wallet.seed = ''
416                 wallet.storage.put('seed', '', True)
417                 wallet.use_encryption = False
418                 wallet.storage.put('use_encryption', wallet.use_encryption, True)
419                 for k in wallet.imported_keys.keys():
420                     wallet.imported_keys[k] = ''
421                 wallet.storage.put('imported_keys', wallet.imported_keys, True)
422                 print_msg("Done.")
423             else:
424                 print_msg("Action canceled.")
425
426     elif cmd.name == 'getconfig':
427         key = args[1]
428         out = config.get(key)
429         print_msg(out)
430
431     elif cmd.name == 'setconfig':
432         key, value = args[1:3]
433         try:
434             value = ast.literal_eval(value)
435         except:
436             pass
437         config.set_key(key, value, True)
438         print_msg(True)
439
440     elif cmd.name == 'password':
441         new_password = prompt_password('New password:')
442         wallet.update_password(password, new_password)
443
444     else:
445         run_command(cmd, password, args)
446
447
448     time.sleep(0.1)
449     sys.exit(0)