add server to daemon print statement
[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 xmlrpclib, socket
112     cmd_runner = Commands(wallet, network)
113     func = getattr(cmd_runner, cmd.name)
114     cmd_runner.password = password
115
116     if cmd.requires_network and not options.offline:
117
118         while True:
119             try:
120                 cmd_runner.network = NetworkProxy(config)
121                 cmd_runner.network.start()
122                 break
123             except socket.error:
124                 if cmd.name != 'daemon':
125                     print "Starting daemon [%s]"%config.get('server')
126                     start_daemon(config)
127                 else:
128                     print "Daemon not running"
129                     sys.exit(1)
130
131         if wallet:
132             wallet.start_threads(cmd_runner.network)
133             wallet.update()
134     else:
135         cmd_runner.network = None
136
137
138     try:
139         result = func(*args[1:])
140     except Exception:
141         print "ecxeption"
142         traceback.print_exc(file=sys.stdout)
143         sys.exit(1)
144
145
146     if cmd.requires_network and not options.offline:
147         if wallet:
148             wallet.stop_threads()
149
150
151     if type(result) == str:
152         util.print_msg(result)
153     elif result is not None:
154         util.print_json(result)
155
156
157
158
159
160
161 if __name__ == '__main__':
162
163     parser = arg_parser()
164     options, args = parser.parse_args()
165     if options.portable and options.wallet_path is None:
166         options.electrum_path = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'electrum_data')
167
168     # config is an object passed to the various constructors (wallet, interface, gui)
169     if is_android:
170         config_options = {
171             'portable': True,
172             'verbose': True,
173             'gui': 'android',
174             'auto_cycle': True,
175         }
176     else:
177         config_options = eval(str(options))
178         for k, v in config_options.items():
179             if v is None:
180                 config_options.pop(k)
181
182     set_verbosity(config_options.get('verbose'))
183
184     config = SimpleConfig(config_options)
185
186     if len(args) == 0:
187         url = None
188         cmd = 'gui'
189     elif len(args) == 1 and re.match('^bitcoin:', args[0]):
190         url = args[0]
191         cmd = 'gui'
192     else:
193         cmd = args[0]
194
195     if cmd == 'gui':
196         gui_name = config.get('gui', 'classic')
197         if gui_name in ['lite', 'classic']:
198             gui_name = 'qt'
199         try:
200             gui = __import__('electrum_gui.' + gui_name, fromlist=['electrum_gui'])
201         except ImportError:
202             traceback.print_exc(file=sys.stdout)
203             sys.exit()
204             #sys.exit("Error: Unknown GUI: " + gui_name )
205
206         # network interface
207         if not options.offline:
208             network = Network(config)
209             network.start()
210         else:
211             network = None
212
213         gui = gui.ElectrumGui(config, network)
214         gui.main(url)
215
216         if network:
217             network.stop()
218
219         # we use daemon threads, their termination is enforced.
220         # this sleep command gives them time to terminate cleanly.
221         time.sleep(0.1)
222         sys.exit(0)
223
224     if cmd not in known_commands:
225         cmd = 'help'
226
227     cmd = known_commands[cmd]
228
229     # instanciate wallet for command-line
230     storage = WalletStorage(config)
231
232
233     if cmd.name in ['create', 'restore']:
234         if storage.file_exists:
235             sys.exit("Error: Remove the existing wallet first!")
236         if options.password is not None:
237             password = options.password
238         elif cmd.name == 'restore' and options.mpk:
239             password = None
240         else:
241             password = prompt_password("Password (hit return if you do not wish to encrypt your wallet):")
242
243         # if config.server is set, the user either passed the server on command line
244         # or chose it previously already. if he didn't pass a server on the command line,
245         # we just pick up a random one.
246         if not config.get('server'):
247             config.set_key('server', pick_random_server())
248
249         #fee = options.tx_fee if options.tx_fee else raw_input("fee (default:%s):" % (str(Decimal(wallet.fee)/100000000)))
250         #gap = options.gap_limit if options.gap_limit else raw_input("gap limit (default 5):")
251         #if fee:
252         #    wallet.set_fee(float(fee)*100000000)
253         #if gap:
254         #    wallet.change_gap_limit(int(gap))
255
256         if cmd.name == 'restore':
257             if options.mpk:
258                 wallet = Wallet.from_mpk(options.mpk, storage)
259             else:
260                 import getpass
261                 seed = getpass.getpass(prompt="seed:", stream=None) if options.concealed else raw_input("seed:")
262                 wallet = Wallet.from_seed(str(seed),storage)
263                 if not wallet:
264                     sys.exit("Error: Invalid seed")
265                 wallet.save_seed(password)
266
267             if not options.offline:
268                 network = Network(config)
269                 network.start()
270                 wallet.start_threads(network)
271                 print_msg("Recovering wallet...")
272                 wallet.restore(lambda x: x)
273                 if wallet.is_found():
274                     print_msg("Recovery successful")
275                 else:
276                     print_msg("Warning: Found no history for this wallet")
277             else:
278                 wallet.synchronize()
279                 print_msg("Warning: This wallet was restored offline. It may contain more addresses than displayed.")
280
281         else:
282             wallet = Wallet(storage)
283             wallet.init_seed(None)
284             wallet.save_seed(password)
285             wallet.synchronize()
286             print_msg("Your wallet generation seed is:\n\"%s\"" % wallet.get_mnemonic(password))
287             print_msg("Please keep it in a safe place; if you lose it, you will not be able to restore your wallet.")
288
289         print_msg("Wallet saved in '%s'" % wallet.storage.path)
290
291         # terminate
292         sys.exit(0)
293
294
295     if cmd.name not in ['create', 'restore'] and cmd.requires_wallet and not storage.file_exists:
296         print_msg("Error: Wallet file not found.")
297         print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
298         sys.exit(0)
299
300
301     if cmd.requires_wallet:
302         wallet = Wallet(storage)
303     else:
304         wallet = None
305
306
307     # important warning
308     if cmd.name in ['dumpprivkey', 'dumpprivkeys']:
309         print_msg("WARNING: ALL your private keys are secret.")
310         print_msg("Exposing a single private key can compromise your entire wallet!")
311         print_msg("In particular, DO NOT use 'redeem private key' services proposed by third parties.")
312
313     # commands needing password
314     if cmd.requires_password:
315         if wallet.seed == '':
316             seed = ''
317             password = None
318         elif wallet.use_encryption:
319             password = prompt_password('Password:', False)
320             if not password:
321                 print_msg("Error: Password required")
322                 sys.exit(1)
323             # check password
324             try:
325                 seed = wallet.get_seed(password)
326             except Exception:
327                 print_msg("Error: This password does not decode this wallet.")
328                 sys.exit(1)
329         else:
330             password = None
331             seed = wallet.get_seed(None)
332     else:
333         password = None
334
335     # add missing arguments, do type conversions
336     if cmd.name == 'importprivkey':
337         # See if they specificed a key on the cmd line, if not prompt
338         if len(args) == 1:
339             args[1] = prompt_password('Enter PrivateKey (will not echo):', False)
340
341     elif cmd.name == 'signrawtransaction':
342         args = [cmd, args[1], json.loads(args[2]) if len(args) > 2 else [], json.loads(args[3]) if len(args) > 3 else []]
343
344     elif cmd.name == 'createmultisig':
345         args = [cmd, int(args[1]), json.loads(args[2])]
346
347     elif cmd.name == 'createrawtransaction':
348         args = [cmd, json.loads(args[1]), json.loads(args[2])]
349
350     elif cmd.name == 'listaddresses':
351         args = [cmd, options.show_all, options.show_labels]
352
353     elif cmd.name in ['payto', 'mktx']:
354         domain = [options.from_addr] if options.from_addr else None
355         args = ['mktx', args[1], Decimal(args[2]), Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain]
356
357     elif cmd.name in ['paytomany', 'mksendmanytx']:
358         domain = [options.from_addr] if options.from_addr else None
359         outputs = []
360         for i in range(1, len(args), 2):
361             if len(args) < i+2:
362                 print_msg("Error: Mismatched arguments.")
363                 sys.exit(1)
364             outputs.append((args[i], Decimal(args[i+1])))
365         args = ['mksendmanytx', outputs, Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain]
366
367     elif cmd.name == 'help':
368         if len(args) < 2:
369             print_help(parser)
370
371     # check the number of arguments
372     if len(args) - 1 < cmd.min_args:
373         print_msg("Not enough arguments")
374         print_msg("Syntax:", cmd.syntax)
375         sys.exit(1)
376
377     if cmd.max_args >= 0 and len(args) - 1 > cmd.max_args:
378         print_msg("too many arguments", args)
379         print_msg("Syntax:", cmd.syntax)
380         sys.exit(1)
381
382     if cmd.max_args < 0:
383         if len(args) > cmd.min_args + 1:
384             message = ' '.join(args[cmd.min_args:])
385             print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message))
386             args = args[0:cmd.min_args] + [message]
387
388
389
390     # run the command
391     if cmd.name == 'deseed':
392         if not wallet.seed:
393             print_msg("Error: This wallet has no seed")
394         else:
395             ns = wallet.storage.path + '.seedless'
396             print_msg("Warning: you are going to create a seedless wallet'\nIt will be saved in '%s'" % ns)
397             if raw_input("Are you sure you want to continue? (y/n) ") in ['y', 'Y', 'yes']:
398                 wallet.storage.path = ns
399                 wallet.seed = ''
400                 wallet.storage.put('seed', '', True)
401                 wallet.use_encryption = False
402                 wallet.storage.put('use_encryption', wallet.use_encryption, True)
403                 for k in wallet.imported_keys.keys():
404                     wallet.imported_keys[k] = ''
405                 wallet.storage.put('imported_keys', wallet.imported_keys, True)
406                 print_msg("Done.")
407             else:
408                 print_msg("Action canceled.")
409
410     elif cmd.name == 'getconfig':
411         key = args[1]
412         out = config.get(key)
413         print_msg(out)
414
415     elif cmd.name == 'setconfig':
416         key, value = args[1:3]
417         try:
418             value = ast.literal_eval(value)
419         except:
420             pass
421         config.set_key(key, value, True)
422         print_msg(True)
423
424     elif cmd.name == 'password':
425         new_password = prompt_password('New password:')
426         wallet.update_password(password, new_password)
427
428     else:
429         run_command(cmd, password, args)
430
431
432     time.sleep(0.1)
433     sys.exit(0)