don't set network.interface to None when disconnetced
[electrum-nvc.git] / lib / wallet.py
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 import sys
20 import base64
21 import os
22 import re
23 import hashlib
24 import copy
25 import operator
26 import ast
27 import threading
28 import random
29 import aes
30 import Queue
31 import time
32
33 from util import print_msg, print_error, format_satoshis
34 from bitcoin import *
35 from account import *
36 from transaction import Transaction
37 from plugins import run_hook
38
39 COINBASE_MATURITY = 100
40 DUST_THRESHOLD = 5430
41
42 # AES encryption
43 EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
44 DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e))
45
46 def pw_encode(s, password):
47     if password:
48         secret = Hash(password)
49         return EncodeAES(secret, s)
50     else:
51         return s
52
53 def pw_decode(s, password):
54     if password is not None:
55         secret = Hash(password)
56         try:
57             d = DecodeAES(secret, s)
58         except Exception:
59             raise Exception('Invalid password')
60         return d
61     else:
62         return s
63
64
65
66
67
68 from version import *
69
70
71 class WalletStorage:
72
73     def __init__(self, config):
74         self.lock = threading.Lock()
75         self.data = {}
76         self.file_exists = False
77         self.path = self.init_path(config)
78         print_error( "wallet path", self.path )
79         if self.path:
80             self.read(self.path)
81
82
83     def init_path(self, config):
84         """Set the path of the wallet."""
85
86         # command line -w option
87         path = config.get('wallet_path')
88         if path:
89             return path
90
91         # path in config file
92         path = config.get('default_wallet_path')
93         if path:
94             return path
95
96         # default path
97         dirpath = os.path.join(config.path, "wallets")
98         if not os.path.exists(dirpath):
99             os.mkdir(dirpath)
100
101         new_path = os.path.join(config.path, "wallets", "default_wallet")
102
103         # default path in pre 1.9 versions
104         old_path = os.path.join(config.path, "electrum.dat")
105         if os.path.exists(old_path) and not os.path.exists(new_path):
106             os.rename(old_path, new_path)
107
108         return new_path
109
110
111     def read(self, path):
112         """Read the contents of the wallet file."""
113         try:
114             with open(self.path, "r") as f:
115                 data = f.read()
116         except IOError:
117             return
118         try:
119             d = ast.literal_eval( data )  #parse raw data from reading wallet file
120         except Exception:
121             raise IOError("Cannot read wallet file.")
122
123         self.data = d
124         self.file_exists = True
125
126
127     def get(self, key, default=None):
128         v = self.data.get(key)
129         if v is None: 
130             v = default
131         return v
132
133     def put(self, key, value, save = True):
134
135         with self.lock:
136             if value is not None:
137                 self.data[key] = value
138             else:
139                 self.data.pop(key)
140             if save: 
141                 self.write()
142
143     def write(self):
144         s = repr(self.data)
145         f = open(self.path,"w")
146         f.write( s )
147         f.close()
148         if 'ANDROID_DATA' not in os.environ:
149             import stat
150             os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE)
151
152
153 class Wallet:
154
155     def __init__(self, storage):
156
157         self.storage = storage
158         self.electrum_version = ELECTRUM_VERSION
159         self.gap_limit_for_change = 3 # constant
160
161         # saved fields
162         self.seed_version          = storage.get('seed_version', SEED_VERSION)
163
164         self.gap_limit             = storage.get('gap_limit', 5)
165         self.use_change            = storage.get('use_change',True)
166         self.use_encryption        = storage.get('use_encryption', False)
167         self.seed                  = storage.get('seed', '')               # encrypted
168         self.labels                = storage.get('labels', {})
169         self.frozen_addresses      = storage.get('frozen_addresses',[])
170         self.addressbook           = storage.get('contacts', [])
171
172         self.imported_keys         = storage.get('imported_keys',{})
173         self.history               = storage.get('addr_history',{})        # address -> list(txid, height)
174
175         self.fee                   = int(storage.get('fee_per_kb',20000))
176
177         self.master_public_keys = storage.get('master_public_keys',{})
178         self.master_private_keys = storage.get('master_private_keys', {})
179
180         self.next_addresses = storage.get('next_addresses',{})
181
182         if self.seed_version not in [4, 6]:
183             msg = "This wallet seed is not supported."
184             if self.seed_version in [5]:
185                 msg += "\nTo open this wallet, try 'git checkout seed_v%d'"%self.seed_version
186                 print msg
187                 sys.exit(1)
188
189         # This attribute is set when wallet.start_threads is called.
190         self.synchronizer = None
191
192         self.load_accounts()
193
194         self.transactions = {}
195         tx_list = self.storage.get('transactions',{})
196         for k,v in tx_list.items():
197             try:
198                 tx = Transaction(v)
199             except Exception:
200                 print_msg("Warning: Cannot deserialize transactions. skipping")
201                 continue
202
203             self.add_extra_addresses(tx)
204             self.transactions[k] = tx
205
206         for h,tx in self.transactions.items():
207             if not self.check_new_tx(h, tx):
208                 print_error("removing unreferenced tx", h)
209                 self.transactions.pop(h)
210
211
212         # not saved
213         self.prevout_values = {}     # my own transaction outputs
214         self.spent_outputs = []
215
216         # spv
217         self.verifier = None
218
219         # there is a difference between wallet.up_to_date and interface.is_up_to_date()
220         # interface.is_up_to_date() returns true when all requests have been answered and processed
221         # wallet.up_to_date is true when the wallet is synchronized (stronger requirement)
222         
223         self.up_to_date = False
224         self.lock = threading.Lock()
225         self.transaction_lock = threading.Lock()
226         self.tx_event = threading.Event()
227
228         for tx_hash, tx in self.transactions.items():
229             self.update_tx_outputs(tx_hash)
230
231
232     def add_extra_addresses(self, tx):
233         h = tx.hash()
234         # find the address corresponding to pay-to-pubkey inputs
235         tx.add_extra_addresses(self.transactions)
236         for o in tx.d.get('outputs'):
237             if o.get('is_pubkey'):
238                 for tx2 in self.transactions.values():
239                     tx2.add_extra_addresses({h:tx})
240
241             
242
243
244     def set_up_to_date(self,b):
245         with self.lock: self.up_to_date = b
246
247
248     def is_up_to_date(self):
249         with self.lock: return self.up_to_date
250
251
252     def update(self):
253         self.up_to_date = False
254         while not self.is_up_to_date(): 
255             time.sleep(0.1)
256
257
258     def import_key(self, sec, password):
259         # check password
260         seed = self.get_seed(password)
261         try:
262             address = address_from_private_key(sec)
263         except Exception:
264             raise Exception('Invalid private key')
265
266         if self.is_mine(address):
267             raise Exception('Address already in wallet')
268         
269         # store the originally requested keypair into the imported keys table
270         self.imported_keys[address] = pw_encode(sec, password )
271         self.storage.put('imported_keys', self.imported_keys, True)
272         if self.synchronizer:
273             self.synchronizer.subscribe_to_addresses([address])
274         return address
275         
276     def delete_imported_key(self, addr):
277         if addr in self.imported_keys:
278             self.imported_keys.pop(addr)
279             self.storage.put('imported_keys', self.imported_keys, True)
280
281
282     def make_seed(self):
283         import mnemonic, ecdsa
284         entropy = ecdsa.util.randrange( pow(2,160) )
285         nonce = 0
286         while True:
287             ss = "%040x"%(entropy+nonce)
288             s = hashlib.sha256(ss.decode('hex')).digest().encode('hex')
289             # we keep only 13 words, that's approximately 139 bits of entropy
290             words = mnemonic.mn_encode(s)[0:13] 
291             seed = ' '.join(words)
292             if mnemonic_hash(seed).startswith(SEED_PREFIX): 
293                 break  # this removes 12 bits of entropy 
294             nonce += 1
295
296         return seed
297
298
299     def init_seed(self, seed):
300         import mnemonic
301         
302         if self.seed: 
303             raise Exception("a seed exists")
304
305         if not seed:
306             self.seed = random_seed(128)
307             self.seed_version = 4
308             return
309
310         #if not seed:
311         #    self.seed = self.make_seed()
312         #    self.seed_version = SEED_VERSION
313         #    return
314
315         # find out what kind of wallet we are
316         try:
317             seed.strip().decode('hex')
318             self.seed_version = 4
319             self.seed = str(seed)
320             return
321         except Exception:
322             pass
323
324         words = seed.split()
325         self.seed_version = 4
326         self.seed = mnemonic.mn_decode(words)
327         
328         #try:
329         #    mnemonic.mn_decode(words)
330         #    uses_electrum_words = True
331         #except Exception:
332         #    uses_electrum_words = False
333         #
334         #if uses_electrum_words and len(words) != 13:
335         #    self.seed_version = 4
336         #    self.seed = mnemonic.mn_decode(words)
337         #else:
338         #    assert mnemonic_hash(seed).startswith(SEED_PREFIX)
339         #    self.seed_version = SEED_VERSION
340         #    self.seed = seed
341             
342
343     def save_seed(self, password):
344         if password: 
345             self.seed = pw_encode( self.seed, password)
346             self.use_encryption = True
347         self.storage.put('seed', self.seed, True)
348         self.storage.put('seed_version', self.seed_version, True)
349         self.storage.put('use_encryption', self.use_encryption,True)
350         self.create_accounts(password)
351
352
353     def create_watching_only_wallet(self, params):
354         K0, c0 = params
355         if not K0:
356             return
357
358         if not c0:
359             self.seed_version = 4
360             self.storage.put('seed_version', self.seed_version, True)
361             self.create_old_account(K0)
362             return
363
364         cK0 = ""
365         self.master_public_keys = {
366             "m/0'/": (c0, K0, cK0),
367             }
368         self.storage.put('master_public_keys', self.master_public_keys, True)
369         self.storage.put('seed_version', self.seed_version, True)
370         self.create_account('1','Main account')
371
372
373     def create_accounts(self, password):
374         seed = pw_decode(self.seed, password)
375
376         if self.seed_version == 4:
377             mpk = OldAccount.mpk_from_seed(seed)
378             self.create_old_account(mpk)
379         else:
380             # create default account
381             self.create_master_keys('1', password)
382             self.create_account('1','Main account')
383
384
385     def create_master_keys(self, account_type, password):
386         master_k, master_c, master_K, master_cK = bip32_init(self.get_seed(None))
387         if account_type == '1':
388             k0, c0, K0, cK0 = bip32_private_derivation(master_k, master_c, "m/", "m/0'/")
389             self.master_public_keys["m/0'/"] = (c0, K0, cK0)
390             self.master_private_keys["m/0'/"] = pw_encode(k0, password)
391         elif account_type == '2of2':
392             k1, c1, K1, cK1 = bip32_private_derivation(master_k, master_c, "m/", "m/1'/")
393             k2, c2, K2, cK2 = bip32_private_derivation(master_k, master_c, "m/", "m/2'/")
394             self.master_public_keys["m/1'/"] = (c1, K1, cK1)
395             self.master_public_keys["m/2'/"] = (c2, K2, cK2)
396             self.master_private_keys["m/1'/"] = pw_encode(k1, password)
397             self.master_private_keys["m/2'/"] = pw_encode(k2, password)
398         elif account_type == '2of3':
399             k3, c3, K3, cK3 = bip32_private_derivation(master_k, master_c, "m/", "m/3'/")
400             k4, c4, K4, cK4 = bip32_private_derivation(master_k, master_c, "m/", "m/4'/")
401             k5, c5, K5, cK5 = bip32_private_derivation(master_k, master_c, "m/", "m/5'/")
402             self.master_public_keys["m/3'/"] = (c3, K3, cK3)
403             self.master_public_keys["m/4'/"] = (c4, K4, cK4)
404             self.master_public_keys["m/5'/"] = (c5, K5, cK5)
405             self.master_private_keys["m/3'/"] = pw_encode(k3, password)
406             self.master_private_keys["m/4'/"] = pw_encode(k4, password)
407             self.master_private_keys["m/5'/"] = pw_encode(k5, password)
408
409         self.storage.put('master_public_keys', self.master_public_keys, True)
410         self.storage.put('master_private_keys', self.master_private_keys, True)
411
412     def has_master_public_keys(self, account_type):
413         if account_type == '1':
414             return "m/0'/" in self.master_public_keys
415         elif account_type == '2of2':
416             return set(["m/1'/", "m/2'/"]) <= set(self.master_public_keys.keys())
417         elif account_type == '2of3':
418             return set(["m/3'/", "m/4'/", "m/5'/"]) <= set(self.master_public_keys.keys())
419
420     def find_root_by_master_key(self, c, K):
421         for key, v in self.master_public_keys.items():
422             if key == "m/":continue
423             cc, KK, _ = v
424             if (c == cc) and (K == KK):
425                 return key
426
427     def deseed_root(self, seed, password):
428         # for safety, we ask the user to enter their seed
429         assert seed == self.get_seed(password)
430         self.seed = ''
431         self.storage.put('seed', '', True)
432
433
434     def deseed_branch(self, k):
435         # check that parent has no seed
436         assert self.seed == ''
437         self.master_private_keys.pop(k)
438         self.storage.put('master_private_keys', self.master_private_keys, True)
439
440     def is_watching_only(self):
441         return (self.seed == '') and (self.master_private_keys == {})
442
443
444
445     def account_id(self, account_type, i):
446         if account_type == '1':
447             return "m/0'/%d"%i
448         elif account_type == '2of2':
449             return "m/1'/%d & m/2'/%d"%(i,i)
450         elif account_type == '2of3':
451             return "m/3'/%d & m/4'/%d & m/5'/%d"%(i,i,i)
452         else:
453             raise Exception('unknown account type')
454
455
456     def num_accounts(self, account_type):
457         keys = self.accounts.keys()
458         i = 0
459         while True:
460             account_id = self.account_id(account_type, i)
461             if account_id not in keys: break
462             i += 1
463         return i
464
465
466     def new_account_address(self, account_type = '1'):
467         i = self.num_accounts(account_type)
468         k = self.account_id(account_type,i)
469
470         addr = self.next_addresses.get(k)
471         if not addr: 
472             account_id, account = self.next_account(account_type)
473             addr = account.first_address()
474             self.next_addresses[k] = addr
475             self.storage.put('next_addresses',self.next_addresses)
476
477         return k, addr
478
479
480     def next_account(self, account_type = '1'):
481
482         i = self.num_accounts(account_type)
483         account_id = self.account_id(account_type,i)
484
485         if account_type is '1':
486             master_c0, master_K0, _ = self.master_public_keys["m/0'/"]
487             c0, K0, cK0 = bip32_public_derivation(master_c0.decode('hex'), master_K0.decode('hex'), "m/0'/", "m/0'/%d"%i)
488             account = BIP32_Account({ 'c':c0, 'K':K0, 'cK':cK0 })
489
490         elif account_type == '2of2':
491             master_c1, master_K1, _ = self.master_public_keys["m/1'/"]
492             c1, K1, cK1 = bip32_public_derivation(master_c1.decode('hex'), master_K1.decode('hex'), "m/1'/", "m/1'/%d"%i)
493             master_c2, master_K2, _ = self.master_public_keys["m/2'/"]
494             c2, K2, cK2 = bip32_public_derivation(master_c2.decode('hex'), master_K2.decode('hex'), "m/2'/", "m/2'/%d"%i)
495             account = BIP32_Account_2of2({ 'c':c1, 'K':K1, 'cK':cK1, 'c2':c2, 'K2':K2, 'cK2':cK2 })
496
497         elif account_type == '2of3':
498             master_c3, master_K3, _ = self.master_public_keys["m/3'/"]
499             c3, K3, cK3 = bip32_public_derivation(master_c3.decode('hex'), master_K3.decode('hex'), "m/3'/", "m/3'/%d"%i)
500             master_c4, master_K4, _ = self.master_public_keys["m/4'/"]
501             c4, K4, cK4 = bip32_public_derivation(master_c4.decode('hex'), master_K4.decode('hex'), "m/4'/", "m/4'/%d"%i)
502             master_c5, master_K5, _ = self.master_public_keys["m/5'/"]
503             c5, K5, cK5 = bip32_public_derivation(master_c5.decode('hex'), master_K5.decode('hex'), "m/5'/", "m/5'/%d"%i)
504             account = BIP32_Account_2of3({ 'c':c3, 'K':K3, 'cK':cK3, 'c2':c4, 'K2':K4, 'cK2':cK4, 'c3':c5, 'K3':K5, 'cK3':cK5 })
505
506         return account_id, account
507
508
509     def set_label(self, name, text = None):
510         changed = False
511         old_text = self.labels.get(name)
512         if text:
513             if old_text != text:
514                 self.labels[name] = text
515                 changed = True
516         else:
517             if old_text:
518                 self.labels.pop(name)
519                 changed = True
520
521         if changed:
522             self.storage.put('labels', self.labels, True)
523
524         run_hook('set_label', name, text, changed)
525         return changed
526
527
528
529     def create_account(self, account_type = '1', name = None):
530         k, account = self.next_account(account_type)
531         if k in self.pending_accounts:
532             self.pending_accounts.pop(k)
533             self.storage.put('pending_accounts', self.pending_accounts)
534
535         self.accounts[k] = account
536         self.save_accounts()
537         if name:
538             self.set_label(k, name)
539
540
541     def create_old_account(self, mpk):
542         self.storage.put('master_public_key', mpk, True)
543         self.accounts[0] = OldAccount({'mpk':mpk, 0:[], 1:[]})
544         self.save_accounts()
545
546
547     def save_accounts(self):
548         d = {}
549         for k, v in self.accounts.items():
550             d[k] = v.dump()
551         self.storage.put('accounts', d, True)
552
553     
554
555     def load_accounts(self):
556         d = self.storage.get('accounts', {})
557         self.accounts = {}
558         for k, v in d.items():
559             if k == 0:
560                 v['mpk'] = self.storage.get('master_public_key')
561                 self.accounts[k] = OldAccount(v)
562             elif '&' in k:
563                 self.accounts[k] = BIP32_Account_2of2(v)
564             else:
565                 self.accounts[k] = BIP32_Account(v)
566
567         self.pending_accounts = self.storage.get('pending_accounts',{})
568
569
570     def delete_pending_account(self, k):
571         self.pending_accounts.pop(k)
572         self.storage.put('pending_accounts', self.pending_accounts)
573
574     def account_is_pending(self, k):
575         return k in self.pending_accounts
576
577     def create_pending_account(self, acct_type, name):
578         k, addr = self.new_account_address(acct_type)
579         self.set_label(k, name)
580         self.pending_accounts[k] = addr
581         self.storage.put('pending_accounts', self.pending_accounts)
582
583     def get_pending_accounts(self):
584         return self.pending_accounts.items()
585
586
587     def addresses(self, include_change = True, _next=True):
588         o = self.get_account_addresses(-1, include_change)
589         for a in self.accounts.keys():
590             o += self.get_account_addresses(a, include_change)
591
592         if _next:
593             for addr in self.next_addresses.values():
594                 if addr not in o:
595                     o += [addr]
596         return o
597
598
599     def is_mine(self, address):
600         return address in self.addresses(True)
601
602
603     def is_change(self, address):
604         if not self.is_mine(address): return False
605         if address in self.imported_keys.keys(): return False
606         acct, s = self.get_address_index(address)
607         if s is None: return False
608         return s[0] == 1
609
610     def get_master_public_key(self):
611         if self.seed_version == 4:
612             return self.storage.get("master_public_key")
613         else:
614             c, K, cK = self.storage.get("master_public_keys")["m/0'/"]
615             return repr((c, K))
616
617     def get_master_private_key(self, account, password):
618         master_k = pw_decode( self.master_private_keys[account], password)
619         master_c, master_K, master_Kc = self.master_public_keys[account]
620         try:
621             K, Kc = get_pubkeys_from_secret(master_k.decode('hex'))
622             assert K.encode('hex') == master_K
623         except Exception:
624             raise Exception("Invalid password")
625         return master_k
626
627
628     def get_address_index(self, address):
629         if address in self.imported_keys.keys():
630             return -1, None
631
632         for account in self.accounts.keys():
633             for for_change in [0,1]:
634                 addresses = self.accounts[account].get_addresses(for_change)
635                 for addr in addresses:
636                     if address == addr:
637                         return account, (for_change, addresses.index(addr))
638
639         for k,v in self.next_addresses.items():
640             if v == address:
641                 return k, (0,0)
642
643         raise Exception("Address not found", address)
644
645
646     def get_roots(self, account):
647         roots = []
648         for a in account.split('&'):
649             s = a.strip()
650             m = re.match("(m/\d+'/)(\d+)", s)
651             roots.append( m.group(1) )
652         return roots
653
654     def is_seeded(self, account):
655         if type(account) is int:
656             return self.seed is not None
657
658         for root in self.get_roots(account):
659             if root not in self.master_private_keys.keys(): 
660                 return False
661         return True
662
663     def rebase_sequence(self, account, sequence):
664         c, i = sequence
665         dd = []
666         for a in account.split('&'):
667             s = a.strip()
668             m = re.match("(m/\d+'/)(\d+)", s)
669             root = m.group(1)
670             num = int(m.group(2))
671             dd.append( (root, [num,c,i] ) )
672         return dd
673         
674
675     def get_keyID(self, account, sequence):
676         if account == 0:
677             a, b = sequence
678             mpk = self.storage.get('master_public_key')
679             return 'old(%s,%d,%d)'%(mpk,a,b)
680
681         rs = self.rebase_sequence(account, sequence)
682         dd = []
683         for root, public_sequence in rs:
684             c, K, _ = self.master_public_keys[root]
685             s = '/' + '/'.join( map(lambda x:str(x), public_sequence) )
686             dd.append( 'bip32(%s,%s,%s)'%(c,K, s) )
687         return '&'.join(dd)
688
689
690
691     def get_seed(self, password):
692         s = pw_decode(self.seed, password)
693         if self.seed_version == 4:
694             seed = s
695             self.accounts[0].check_seed(seed)
696         else:
697             seed = mnemonic_hash(s)
698         return seed
699         
700
701     def get_mnemonic(self, password):
702         import mnemonic
703         s = pw_decode(self.seed, password)
704         if self.seed_version == 4:
705             return ' '.join(mnemonic.mn_encode(s))
706         else:
707             return s
708
709         
710
711     def get_private_key(self, address, password):
712         if self.is_watching_only():
713             return []
714
715         # first check the provided password
716         seed = self.get_seed(password)
717         
718         out = []
719         if address in self.imported_keys.keys():
720             out.append( pw_decode( self.imported_keys[address], password ) )
721         else:
722             account, sequence = self.get_address_index(address)
723             if account == 0:
724                 pk = self.accounts[account].get_private_key(seed, sequence)
725                 out.append(pk)
726                 return out
727
728             # assert address == self.accounts[account].get_address(*sequence)
729             rs = self.rebase_sequence( account, sequence)
730             for root, public_sequence in rs:
731
732                 if root not in self.master_private_keys.keys(): continue
733                 master_k = self.get_master_private_key(root, password)
734                 master_c, _, _ = self.master_public_keys[root]
735                 pk = bip32_private_key( public_sequence, master_k.decode('hex'), master_c.decode('hex'))
736                 out.append(pk)
737                     
738         return out
739
740
741     def add_keypairs_from_wallet(self, tx, keypairs, password):
742         for txin in tx.inputs:
743             address = txin['address']
744             private_keys = self.get_private_key(address, password)
745             for sec in private_keys:
746                 pubkey = public_key_from_private_key(sec)
747                 keypairs[ pubkey ] = sec
748                 if address in self.imported_keys.keys():
749                     txin['redeemPubkey'] = pubkey
750
751
752     def add_keypairs_from_KeyID(self, tx, keypairs, password):
753         # first check the provided password
754         seed = self.get_seed(password)
755
756         for txin in tx.inputs:
757             keyid = txin.get('KeyID')
758             if keyid:
759
760                 if self.seed_version == 4:
761                     m = re.match("old\(([0-9a-f]+),(\d+),(\d+)", keyid)
762                     if not m: continue
763                     mpk = m.group(1)
764                     if mpk != self.storage.get('master_public_key'): continue 
765                     for_change = int(m.group(2))
766                     num = int(m.group(3))
767                     account = self.accounts[0]
768                     addr = account.get_address(for_change, num)
769                     txin['address'] = addr # fixme: side effect
770                     pk = account.get_private_key(seed, (for_change, num))
771                     pubkey = public_key_from_private_key(pk)
772                     keypairs[pubkey] = pk
773                     continue
774
775
776                 roots = []
777                 for s in keyid.split('&'):
778                     m = re.match("bip32\(([0-9a-f]+),([0-9a-f]+),(/\d+/\d+/\d+)", s)
779                     if not m: continue
780                     c = m.group(1)
781                     K = m.group(2)
782                     sequence = m.group(3)
783                     root = self.find_root_by_master_key(c,K)
784                     if not root: continue
785                     sequence = map(lambda x:int(x), sequence.strip('/').split('/'))
786                     root = root + '%d'%sequence[0]
787                     sequence = sequence[1:]
788                     roots.append((root,sequence)) 
789
790                 account_id = " & ".join( map(lambda x:x[0], roots) )
791                 account = self.accounts.get(account_id)
792                 if not account: continue
793                 addr = account.get_address(*sequence)
794                 txin['address'] = addr # fixme: side effect
795                 pk = self.get_private_key(addr, password)
796                 for sec in pk:
797                     pubkey = public_key_from_private_key(sec)
798                     keypairs[pubkey] = sec
799
800
801
802     def signrawtransaction(self, tx, input_info, private_keys, password):
803
804         # check that the password is correct
805         seed = self.get_seed(password)
806
807         # add input info
808         tx.add_input_info(input_info)
809
810         # add redeem script for coins that are in the wallet
811         # FIXME: add redeemPubkey too!
812         unspent_coins = self.get_unspent_coins()
813         for txin in tx.inputs:
814             for item in unspent_coins:
815                 if txin['prevout_hash'] == item['prevout_hash'] and txin['prevout_n'] == item['prevout_n']:
816                     print_error( "tx input is in unspent coins" )
817                     txin['scriptPubKey'] = item['scriptPubKey']
818                     account, sequence = self.get_address_index(item['address'])
819                     if account != -1:
820                         txin['redeemScript'] = self.accounts[account].redeem_script(sequence)
821                         print_error("added redeemScript", txin['redeemScript'])
822                     break
823
824
825         # build a list of public/private keys
826         keypairs = {}
827
828         # add private keys from parameter
829         for sec in private_keys:
830             pubkey = public_key_from_private_key(sec)
831             keypairs[ pubkey ] = sec
832
833         # add private_keys from KeyID
834         self.add_keypairs_from_KeyID(tx, keypairs, password)
835
836         # add private keys from wallet
837         self.add_keypairs_from_wallet(tx, keypairs, password)
838         self.sign_transaction(tx, keypairs)
839
840
841     def sign_message(self, address, message, password):
842         keys = self.get_private_key(address, password)
843         assert len(keys) == 1
844         sec = keys[0]
845         key = regenerate_key(sec)
846         compressed = is_compressed(sec)
847         return key.sign_message(message, compressed, address)
848
849
850     def change_gap_limit(self, value):
851         if value >= self.gap_limit:
852             self.gap_limit = value
853             self.storage.put('gap_limit', self.gap_limit, True)
854             #self.interface.poke('synchronizer')
855             return True
856
857         elif value >= self.min_acceptable_gap():
858             for key, account in self.accounts.items():
859                 addresses = account[0]
860                 k = self.num_unused_trailing_addresses(addresses)
861                 n = len(addresses) - k + value
862                 addresses = addresses[0:n]
863                 self.accounts[key][0] = addresses
864
865             self.gap_limit = value
866             self.storage.put('gap_limit', self.gap_limit, True)
867             self.save_accounts()
868             return True
869         else:
870             return False
871
872     def num_unused_trailing_addresses(self, addresses):
873         k = 0
874         for a in addresses[::-1]:
875             if self.history.get(a):break
876             k = k + 1
877         return k
878
879     def min_acceptable_gap(self):
880         # fixme: this assumes wallet is synchronized
881         n = 0
882         nmax = 0
883
884         for account in self.accounts.values():
885             addresses = account.get_addresses(0)
886             k = self.num_unused_trailing_addresses(addresses)
887             for a in addresses[0:-k]:
888                 if self.history.get(a):
889                     n = 0
890                 else:
891                     n += 1
892                     if n > nmax: nmax = n
893         return nmax + 1
894
895
896     def address_is_old(self, address):
897         age = -1
898         h = self.history.get(address, [])
899         if h == ['*']:
900             return True
901         for tx_hash, tx_height in h:
902             if tx_height == 0:
903                 tx_age = 0
904             else: 
905                 tx_age = self.verifier.blockchain.height() - tx_height + 1
906             if tx_age > age:
907                 age = tx_age
908         return age > 2
909
910
911     def synchronize_sequence(self, account, for_change):
912         limit = self.gap_limit_for_change if for_change else self.gap_limit
913         new_addresses = []
914         while True:
915             addresses = account.get_addresses(for_change)
916             if len(addresses) < limit:
917                 address = account.create_new_address(for_change)
918                 self.history[address] = []
919                 new_addresses.append( address )
920                 continue
921
922             if map( lambda a: self.address_is_old(a), addresses[-limit:] ) == limit*[False]:
923                 break
924             else:
925                 address = account.create_new_address(for_change)
926                 self.history[address] = []
927                 new_addresses.append( address )
928
929         return new_addresses
930         
931
932
933     def create_pending_accounts(self):
934         for account_type in ['1','2of2','2of3']:
935             if not self.has_master_public_keys(account_type):
936                 continue
937             k, a = self.new_account_address(account_type)
938             if self.address_is_old(a):
939                 print_error( "creating account", a )
940                 self.create_account(account_type)
941                 self.next_addresses.pop(k)
942
943
944     def synchronize_account(self, account):
945         new = []
946         new += self.synchronize_sequence(account, 0)
947         new += self.synchronize_sequence(account, 1)
948         return new
949
950
951     def synchronize(self):
952         if self.master_public_keys:
953             self.create_pending_accounts()
954         new = []
955         for account in self.accounts.values():
956             new += self.synchronize_account(account)
957         if new:
958             self.save_accounts()
959             self.storage.put('addr_history', self.history, True)
960         return new
961
962
963     def is_found(self):
964         return self.history.values() != [[]] * len(self.history) 
965
966
967     def add_contact(self, address, label=None):
968         self.addressbook.append(address)
969         self.storage.put('contacts', self.addressbook, True)
970         if label:  
971             self.set_label(address, label)
972
973
974     def delete_contact(self, addr):
975         if addr in self.addressbook:
976             self.addressbook.remove(addr)
977             self.storage.put('addressbook', self.addressbook, True)
978
979
980     def fill_addressbook(self):
981         for tx_hash, tx in self.transactions.items():
982             is_relevant, is_send, _, _ = self.get_tx_value(tx)
983             if is_send:
984                 for addr, v in tx.outputs:
985                     if not self.is_mine(addr) and addr not in self.addressbook:
986                         self.addressbook.append(addr)
987         # redo labels
988         # self.update_tx_labels()
989
990     def get_num_tx(self, address):
991         n = 0 
992         for tx in self.transactions.values():
993             if address in map(lambda x:x[0], tx.outputs): n += 1
994         return n
995
996
997     def get_address_flags(self, addr):
998         flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" 
999         flags += "F" if addr in self.frozen_addresses else "-"
1000         return flags
1001         
1002
1003     def get_tx_value(self, tx, account=None):
1004         domain = self.get_account_addresses(account)
1005         return tx.get_value(domain, self.prevout_values)
1006
1007     
1008     def update_tx_outputs(self, tx_hash):
1009         tx = self.transactions.get(tx_hash)
1010
1011         for i, (addr, value) in enumerate(tx.outputs):
1012             key = tx_hash+ ':%d'%i
1013             self.prevout_values[key] = value
1014
1015         for item in tx.inputs:
1016             if self.is_mine(item.get('address')):
1017                 key = item['prevout_hash'] + ':%d'%item['prevout_n']
1018                 self.spent_outputs.append(key)
1019
1020
1021     def get_addr_balance(self, address):
1022         assert self.is_mine(address)
1023         h = self.history.get(address,[])
1024         if h == ['*']: return 0,0
1025         c = u = 0
1026         received_coins = []   # list of coins received at address
1027
1028         for tx_hash, tx_height in h:
1029             tx = self.transactions.get(tx_hash)
1030             if not tx: continue
1031
1032             for i, (addr, value) in enumerate(tx.outputs):
1033                 if addr == address:
1034                     key = tx_hash + ':%d'%i
1035                     received_coins.append(key)
1036
1037         for tx_hash, tx_height in h:
1038             tx = self.transactions.get(tx_hash)
1039             if not tx: continue
1040             v = 0
1041
1042             for item in tx.inputs:
1043                 addr = item.get('address')
1044                 if addr == address:
1045                     key = item['prevout_hash']  + ':%d'%item['prevout_n']
1046                     value = self.prevout_values.get( key )
1047                     if key in received_coins: 
1048                         v -= value
1049
1050             for i, (addr, value) in enumerate(tx.outputs):
1051                 key = tx_hash + ':%d'%i
1052                 if addr == address:
1053                     v += value
1054
1055             if tx_height:
1056                 c += v
1057             else:
1058                 u += v
1059         return c, u
1060
1061
1062     def get_account_name(self, k):
1063         if k == 0:
1064             if self.seed_version == 4: 
1065                 name = 'Main account'
1066             else:
1067                 name = 'Old account'
1068         else:
1069             default = "Unnamed account"
1070             m = re.match("m/0'/(\d+)", k)
1071             if m:
1072                 num = m.group(1)
1073                 if num == '0':
1074                     default = "Main account"
1075                 else:
1076                     default = "Account %s"%num
1077                     
1078             m = re.match("m/1'/(\d+) & m/2'/(\d+)", k)
1079             if m:
1080                 num = m.group(1)
1081                 default = "2of2 account %s"%num
1082             name = self.labels.get(k, default)
1083
1084         return name
1085
1086     def get_account_names(self):
1087         accounts = {}
1088         for k, account in self.accounts.items():
1089             accounts[k] = self.get_account_name(k)
1090         if self.imported_keys:
1091             accounts[-1] = 'Imported keys'
1092         return accounts
1093
1094     def get_account_addresses(self, a, include_change=True):
1095         if a is None:
1096             o = self.addresses(True)
1097         elif a == -1:
1098             o = self.imported_keys.keys()
1099         else:
1100             ac = self.accounts[a]
1101             o = ac.get_addresses(0)
1102             if include_change: o += ac.get_addresses(1)
1103         return o
1104
1105     def get_imported_balance(self):
1106         return self.get_balance(self.imported_keys.keys())
1107
1108     def get_account_balance(self, account):
1109         return self.get_balance(self.get_account_addresses(account))
1110
1111     def get_frozen_balance(self):
1112         return self.get_balance(self.frozen_addresses)
1113         
1114     def get_balance(self, domain=None):
1115         if domain is None: domain = self.addresses(True)
1116         cc = uu = 0
1117         for addr in domain:
1118             c, u = self.get_addr_balance(addr)
1119             cc += c
1120             uu += u
1121         return cc, uu
1122
1123
1124     def get_unspent_coins(self, domain=None):
1125         coins = []
1126         if domain is None: domain = self.addresses(True)
1127         for addr in domain:
1128             h = self.history.get(addr, [])
1129             if h == ['*']: continue
1130             for tx_hash, tx_height in h:
1131                 tx = self.transactions.get(tx_hash)
1132                 if tx is None: raise Exception("Wallet not synchronized")
1133                 is_coinbase = tx.inputs[0].get('prevout_hash') == '0'*64
1134                 for o in tx.d.get('outputs'):
1135                     output = o.copy()
1136                     if output.get('address') != addr: continue
1137                     key = tx_hash + ":%d" % output.get('prevout_n')
1138                     if key in self.spent_outputs: continue
1139                     output['prevout_hash'] = tx_hash
1140                     output['height'] = tx_height
1141                     output['coinbase'] = is_coinbase
1142                     coins.append((tx_height, output))
1143
1144         # sort by age
1145         if coins:
1146             coins = sorted(coins)
1147             if coins[-1][0] != 0:
1148                 while coins[0][0] == 0: 
1149                     coins = coins[1:] + [ coins[0] ]
1150         return [x[1] for x in coins]
1151
1152
1153     def choose_tx_inputs( self, amount, fixed_fee, domain = None ):
1154         """ todo: minimize tx size """
1155         total = 0
1156         fee = self.fee if fixed_fee is None else fixed_fee
1157         if domain is None:
1158             domain = self.addresses(True)
1159
1160         for i in self.frozen_addresses:
1161             if i in domain: domain.remove(i)
1162
1163         coins = self.get_unspent_coins(domain)
1164         inputs = []
1165
1166         for item in coins:
1167             if item.get('coinbase') and item.get('height') + COINBASE_MATURITY > self.network.blockchain.height:
1168                 continue
1169             addr = item.get('address')
1170             v = item.get('value')
1171             total += v
1172             inputs.append(item)
1173             fee = self.estimated_fee(inputs) if fixed_fee is None else fixed_fee
1174             if total >= amount + fee: break
1175         else:
1176             inputs = []
1177
1178         return inputs, total, fee
1179
1180
1181     def set_fee(self, fee):
1182         if self.fee != fee:
1183             self.fee = fee
1184             self.storage.put('fee_per_kb', self.fee, True)
1185         
1186     def estimated_fee(self, inputs):
1187         estimated_size =  len(inputs) * 180 + 80     # this assumes non-compressed keys
1188         fee = self.fee * int(round(estimated_size/1024.))
1189         if fee == 0: fee = self.fee
1190         return fee
1191
1192
1193     def add_tx_change( self, inputs, outputs, amount, fee, total, change_addr=None):
1194         "add change to a transaction"
1195         change_amount = total - ( amount + fee )
1196         if change_amount > DUST_THRESHOLD:
1197             if not change_addr:
1198
1199                 # send change to one of the accounts involved in the tx
1200                 address = inputs[0].get('address')
1201                 account, _ = self.get_address_index(address)
1202
1203                 if not self.use_change or account == -1:
1204                     change_addr = inputs[-1]['address']
1205                 else:
1206                     change_addr = self.accounts[account].get_addresses(1)[-self.gap_limit_for_change]
1207
1208             # Insert the change output at a random position in the outputs
1209             posn = random.randint(0, len(outputs))
1210             outputs[posn:posn] = [( change_addr,  change_amount)]
1211         return outputs
1212
1213
1214     def get_history(self, address):
1215         with self.lock:
1216             return self.history.get(address)
1217
1218
1219     def get_status(self, h):
1220         if not h: return None
1221         if h == ['*']: return '*'
1222         status = ''
1223         for tx_hash, height in h:
1224             status += tx_hash + ':%d:' % height
1225         return hashlib.sha256( status ).digest().encode('hex')
1226
1227
1228     def receive_tx_callback(self, tx_hash, tx, tx_height):
1229
1230         with self.transaction_lock:
1231             self.add_extra_addresses(tx)
1232             if not self.check_new_tx(tx_hash, tx):
1233                 # may happen due to pruning
1234                 print_error("received transaction that is no longer referenced in history", tx_hash)
1235                 return
1236             self.transactions[tx_hash] = tx
1237             self.network.interface.pending_transactions_for_notifications.append(tx)
1238             self.save_transactions()
1239             if self.verifier and tx_height>0: 
1240                 self.verifier.add(tx_hash, tx_height)
1241             self.update_tx_outputs(tx_hash)
1242
1243
1244     def save_transactions(self):
1245         tx = {}
1246         for k,v in self.transactions.items():
1247             tx[k] = str(v)
1248         self.storage.put('transactions', tx, True)
1249
1250     def receive_history_callback(self, addr, hist):
1251
1252         if not self.check_new_history(addr, hist):
1253             raise Exception("error: received history for %s is not consistent with known transactions"%addr)
1254             
1255         with self.lock:
1256             self.history[addr] = hist
1257             self.storage.put('addr_history', self.history, True)
1258
1259         if hist != ['*']:
1260             for tx_hash, tx_height in hist:
1261                 if tx_height>0:
1262                     # add it in case it was previously unconfirmed
1263                     if self.verifier: self.verifier.add(tx_hash, tx_height)
1264
1265
1266     def get_tx_history(self, account=None):
1267         if not self.verifier:
1268             return []
1269
1270         with self.transaction_lock:
1271             history = self.transactions.items()
1272             history.sort(key = lambda x: self.verifier.get_txpos(x[0]))
1273             result = []
1274     
1275             balance = 0
1276             for tx_hash, tx in history:
1277                 is_relevant, is_mine, v, fee = self.get_tx_value(tx, account)
1278                 if v is not None: balance += v
1279
1280             c, u = self.get_account_balance(account)
1281
1282             if balance != c+u:
1283                 result.append( ('', 1000, 0, c+u-balance, None, c+u-balance, None ) )
1284
1285             balance = c + u - balance
1286             for tx_hash, tx in history:
1287                 is_relevant, is_mine, value, fee = self.get_tx_value(tx, account)
1288                 if not is_relevant:
1289                     continue
1290                 if value is not None:
1291                     balance += value
1292
1293                 conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None)
1294                 result.append( (tx_hash, conf, is_mine, value, fee, balance, timestamp) )
1295
1296         return result
1297
1298
1299     def get_label(self, tx_hash):
1300         label = self.labels.get(tx_hash)
1301         is_default = (label == '') or (label is None)
1302         if is_default: label = self.get_default_label(tx_hash)
1303         return label, is_default
1304
1305
1306     def get_default_label(self, tx_hash):
1307         tx = self.transactions.get(tx_hash)
1308         default_label = ''
1309         if tx:
1310             is_relevant, is_mine, _, _ = self.get_tx_value(tx)
1311             if is_mine:
1312                 for o in tx.outputs:
1313                     o_addr, _ = o
1314                     if not self.is_mine(o_addr):
1315                         try:
1316                             default_label = self.labels[o_addr]
1317                         except KeyError:
1318                             default_label = o_addr
1319                         break
1320                 else:
1321                     default_label = '(internal)'
1322             else:
1323                 for o in tx.outputs:
1324                     o_addr, _ = o
1325                     if self.is_mine(o_addr) and not self.is_change(o_addr):
1326                         break
1327                 else:
1328                     for o in tx.outputs:
1329                         o_addr, _ = o
1330                         if self.is_mine(o_addr):
1331                             break
1332                     else:
1333                         o_addr = None
1334
1335                 if o_addr:
1336                     dest_label = self.labels.get(o_addr)
1337                     try:
1338                         default_label = self.labels[o_addr]
1339                     except KeyError:
1340                         default_label = o_addr
1341
1342         return default_label
1343
1344
1345     def make_unsigned_transaction(self, outputs, fee=None, change_addr=None, domain=None ):
1346         for address, x in outputs:
1347             assert is_valid(address)
1348         amount = sum( map(lambda x:x[1], outputs) )
1349         inputs, total, fee = self.choose_tx_inputs( amount, fee, domain )
1350         if not inputs:
1351             raise ValueError("Not enough funds")
1352         self.add_input_info(inputs)
1353         outputs = self.add_tx_change(inputs, outputs, amount, fee, total, change_addr)
1354         return Transaction.from_io(inputs, outputs)
1355
1356
1357     def mktx(self, outputs, password, fee=None, change_addr=None, domain= None ):
1358         tx = self.make_unsigned_transaction(outputs, fee, change_addr, domain)
1359         keypairs = {}
1360         self.add_keypairs_from_wallet(tx, keypairs, password)
1361         if keypairs:
1362             self.sign_transaction(tx, keypairs)
1363         return tx
1364
1365
1366     def add_input_info(self, inputs):
1367         for txin in inputs:
1368             address = txin['address']
1369             if address in self.imported_keys.keys():
1370                 continue
1371             account, sequence = self.get_address_index(address)
1372             txin['KeyID'] = self.get_keyID(account, sequence)
1373             redeemScript = self.accounts[account].redeem_script(sequence)
1374             if redeemScript: 
1375                 txin['redeemScript'] = redeemScript
1376             else:
1377                 txin['redeemPubkey'] = self.accounts[account].get_pubkey(*sequence)
1378
1379
1380     def sign_transaction(self, tx, keypairs):
1381         tx.sign(keypairs)
1382         run_hook('sign_transaction', tx)
1383
1384
1385     def sendtx(self, tx):
1386         # synchronous
1387         h = self.send_tx(tx)
1388         self.tx_event.wait()
1389         return self.receive_tx(h)
1390
1391     def send_tx(self, tx):
1392         # asynchronous
1393         self.tx_event.clear()
1394         self.network.interface.send([('blockchain.transaction.broadcast', [str(tx)])], self.on_broadcast)
1395         return tx.hash()
1396
1397     def on_broadcast(self, i, r):
1398         self.tx_result = r.get('result')
1399         self.tx_event.set()
1400
1401     def receive_tx(self,tx_hash):
1402         out = self.tx_result 
1403         if out != tx_hash:
1404             return False, "error: " + out
1405         return True, out
1406
1407
1408
1409     def update_password(self, old_password, new_password):
1410         if new_password == '': new_password = None
1411         decoded = self.get_seed(old_password)
1412         self.seed = pw_encode( decoded, new_password)
1413         self.storage.put('seed', self.seed, True)
1414         self.use_encryption = (new_password != None)
1415         self.storage.put('use_encryption', self.use_encryption,True)
1416         for k in self.imported_keys.keys():
1417             a = self.imported_keys[k]
1418             b = pw_decode(a, old_password)
1419             c = pw_encode(b, new_password)
1420             self.imported_keys[k] = c
1421         self.storage.put('imported_keys', self.imported_keys, True)
1422
1423         for k, v in self.master_private_keys.items():
1424             b = pw_decode(v, old_password)
1425             c = pw_encode(b, new_password)
1426             self.master_private_keys[k] = c
1427         self.storage.put('master_private_keys', self.master_private_keys, True)
1428
1429
1430     def freeze(self,addr):
1431         if self.is_mine(addr) and addr not in self.frozen_addresses:
1432             self.frozen_addresses.append(addr)
1433             self.storage.put('frozen_addresses', self.frozen_addresses, True)
1434             return True
1435         else:
1436             return False
1437
1438     def unfreeze(self,addr):
1439         if self.is_mine(addr) and addr in self.frozen_addresses:
1440             self.frozen_addresses.remove(addr)
1441             self.storage.put('frozen_addresses', self.frozen_addresses, True)
1442             return True
1443         else:
1444             return False
1445
1446
1447     def set_verifier(self, verifier):
1448         self.verifier = verifier
1449
1450         # review transactions that are in the history
1451         for addr, hist in self.history.items():
1452             if hist == ['*']: continue
1453             for tx_hash, tx_height in hist:
1454                 if tx_height>0:
1455                     # add it in case it was previously unconfirmed
1456                     self.verifier.add(tx_hash, tx_height)
1457
1458
1459         # if we are on a pruning server, remove unverified transactions
1460         vr = self.verifier.transactions.keys() + self.verifier.verified_tx.keys()
1461         for tx_hash in self.transactions.keys():
1462             if tx_hash not in vr:
1463                 self.transactions.pop(tx_hash)
1464
1465
1466
1467     def check_new_history(self, addr, hist):
1468         
1469         # check that all tx in hist are relevant
1470         if hist != ['*']:
1471             for tx_hash, height in hist:
1472                 tx = self.transactions.get(tx_hash)
1473                 if not tx: continue
1474                 if not tx.has_address(addr):
1475                     return False
1476
1477         # check that we are not "orphaning" a transaction
1478         old_hist = self.history.get(addr,[])
1479         if old_hist == ['*']: return True
1480
1481         for tx_hash, height in old_hist:
1482             if tx_hash in map(lambda x:x[0], hist): continue
1483             found = False
1484             for _addr, _hist in self.history.items():
1485                 if _addr == addr: continue
1486                 if _hist == ['*']: continue
1487                 _tx_hist = map(lambda x:x[0], _hist)
1488                 if tx_hash in _tx_hist:
1489                     found = True
1490                     break
1491
1492             if not found:
1493                 tx = self.transactions.get(tx_hash)
1494                 # tx might not be there
1495                 if not tx: continue
1496                 
1497                 # already verified?
1498                 if self.verifier.get_height(tx_hash):
1499                     continue
1500                 # unconfirmed tx
1501                 print_error("new history is orphaning transaction:", tx_hash)
1502                 # check that all outputs are not mine, request histories
1503                 ext_requests = []
1504                 for _addr, _v in tx.outputs:
1505                     # assert not self.is_mine(_addr)
1506                     ext_requests.append( ('blockchain.address.get_history', [_addr]) )
1507
1508                 ext_h = self.network.synchronous_get(ext_requests)
1509                 print_error("sync:", ext_requests, ext_h)
1510                 height = None
1511                 for h in ext_h:
1512                     if h == ['*']: continue
1513                     for item in h:
1514                         if item.get('tx_hash') == tx_hash:
1515                             height = item.get('height')
1516                 if height:
1517                     print_error("found height for", tx_hash, height)
1518                     self.verifier.add(tx_hash, height)
1519                 else:
1520                     print_error("removing orphaned tx from history", tx_hash)
1521                     self.transactions.pop(tx_hash)
1522
1523         return True
1524
1525
1526
1527     def check_new_tx(self, tx_hash, tx):
1528         # 1 check that tx is referenced in addr_history. 
1529         addresses = []
1530         for addr, hist in self.history.items():
1531             if hist == ['*']:continue
1532             for txh, height in hist:
1533                 if txh == tx_hash: 
1534                     addresses.append(addr)
1535
1536         if not addresses:
1537             return False
1538
1539         # 2 check that referencing addresses are in the tx
1540         for addr in addresses:
1541             if not tx.has_address(addr):
1542                 return False
1543
1544         return True
1545
1546
1547     def start_threads(self, network):
1548         from verifier import TxVerifier
1549         self.network = network
1550         if self.network:
1551             self.verifier = TxVerifier(self.network, self.storage)
1552             self.verifier.start()
1553             self.set_verifier(self.verifier)
1554             self.synchronizer = WalletSynchronizer(self, network)
1555             self.synchronizer.start()
1556         else:
1557             self.verifier = None
1558             self.synchronizer =None
1559
1560     def stop_threads(self):
1561         if self.network:
1562             self.verifier.stop()
1563             self.synchronizer.stop()
1564
1565
1566     def restore(self, callback):
1567         from i18n import _
1568         def wait_for_wallet():
1569             self.set_up_to_date(False)
1570             while not self.is_up_to_date():
1571                 msg = "%s\n%s %d\n%s %.1f"%(
1572                     _("Please wait..."),
1573                     _("Addresses generated:"),
1574                     len(self.addresses(True)), 
1575                     _("Kilobytes received:"), 
1576                     self.network.interface.bytes_received/1024.)
1577
1578                 apply(callback, (msg,))
1579                 time.sleep(0.1)
1580
1581         def wait_for_network():
1582             while not self.network.is_connected():
1583                 msg = "%s \n" % (_("Connecting..."))
1584                 apply(callback, (msg,))
1585                 time.sleep(0.1)
1586
1587         # wait until we are connected, because the user might have selected another server
1588         if self.network:
1589             wait_for_network()
1590             wait_for_wallet()
1591         else:
1592             self.synchronize()
1593             
1594         self.fill_addressbook()
1595
1596
1597
1598
1599 class WalletSynchronizer(threading.Thread):
1600
1601
1602     def __init__(self, wallet, network):
1603         threading.Thread.__init__(self)
1604         self.daemon = True
1605         self.wallet = wallet
1606         self.network = network
1607         self.was_updated = True
1608         self.running = False
1609         self.lock = threading.Lock()
1610         self.queue = Queue.Queue()
1611
1612     def stop(self):
1613         with self.lock: self.running = False
1614
1615     def is_running(self):
1616         with self.lock: return self.running
1617
1618     
1619     def subscribe_to_addresses(self, addresses):
1620         messages = []
1621         for addr in addresses:
1622             messages.append(('blockchain.address.subscribe', [addr]))
1623         self.network.subscribe( messages, lambda i,r: self.queue.put(r))
1624
1625
1626     def run(self):
1627         with self.lock:
1628             self.running = True
1629
1630         while self.is_running():
1631
1632             if not self.network.is_connected():
1633                 self.network.wait_until_connected()
1634                 
1635             self.run_interface(self.network.interface)
1636
1637
1638     def run_interface(self, interface):
1639
1640         print_error("synchronizer: connected to", interface.server)
1641
1642         requested_tx = []
1643         missing_tx = []
1644         requested_histories = {}
1645
1646         # request any missing transactions
1647         for history in self.wallet.history.values():
1648             if history == ['*']: continue
1649             for tx_hash, tx_height in history:
1650                 if self.wallet.transactions.get(tx_hash) is None and (tx_hash, tx_height) not in missing_tx:
1651                     missing_tx.append( (tx_hash, tx_height) )
1652
1653         if missing_tx:
1654             print_error("missing tx", missing_tx)
1655
1656         # subscriptions
1657         self.subscribe_to_addresses(self.wallet.addresses(True))
1658
1659         while self.is_running():
1660             # 1. create new addresses
1661             new_addresses = self.wallet.synchronize()
1662
1663             # request missing addresses
1664             if new_addresses:
1665                 self.subscribe_to_addresses(new_addresses)
1666
1667             # request missing transactions
1668             for tx_hash, tx_height in missing_tx:
1669                 if (tx_hash, tx_height) not in requested_tx:
1670                     interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], lambda i,r: self.queue.put(r))
1671                     requested_tx.append( (tx_hash, tx_height) )
1672             missing_tx = []
1673
1674             # detect if situation has changed
1675             if interface.is_up_to_date() and self.queue.empty():
1676                 if not self.wallet.is_up_to_date():
1677                     self.wallet.set_up_to_date(True)
1678                     self.was_updated = True
1679             else:
1680                 if self.wallet.is_up_to_date():
1681                     self.wallet.set_up_to_date(False)
1682                     self.was_updated = True
1683
1684             if self.was_updated:
1685                 self.wallet.network.trigger_callback('updated')
1686                 self.was_updated = False
1687
1688             # 2. get a response
1689             try:
1690                 r = self.queue.get(block=True, timeout=1)
1691             except Queue.Empty:
1692                 continue
1693
1694             if interface != self.network.interface:
1695                 break
1696             
1697             if not r:
1698                 continue
1699
1700             # 3. handle response
1701             method = r['method']
1702             params = r['params']
1703             result = r.get('result')
1704             error = r.get('error')
1705             if error:
1706                 print "error", r
1707                 continue
1708
1709             if method == 'blockchain.address.subscribe':
1710                 addr = params[0]
1711                 if self.wallet.get_status(self.wallet.get_history(addr)) != result:
1712                     if requested_histories.get(addr) is None:
1713                         interface.send([('blockchain.address.get_history', [addr])], lambda i,r:self.queue.put(r))
1714                         requested_histories[addr] = result
1715
1716             elif method == 'blockchain.address.get_history':
1717                 addr = params[0]
1718                 print_error("receiving history", addr, result)
1719                 if result == ['*']:
1720                     assert requested_histories.pop(addr) == '*'
1721                     self.wallet.receive_history_callback(addr, result)
1722                 else:
1723                     hist = []
1724                     # check that txids are unique
1725                     txids = []
1726                     for item in result:
1727                         tx_hash = item['tx_hash']
1728                         if tx_hash not in txids:
1729                             txids.append(tx_hash)
1730                             hist.append( (tx_hash, item['height']) )
1731
1732                     if len(hist) != len(result):
1733                         raise Exception("error: server sent history with non-unique txid", result)
1734
1735                     # check that the status corresponds to what was announced
1736                     rs = requested_histories.pop(addr)
1737                     if self.wallet.get_status(hist) != rs:
1738                         raise Exception("error: status mismatch: %s"%addr)
1739                 
1740                     # store received history
1741                     self.wallet.receive_history_callback(addr, hist)
1742
1743                     # request transactions that we don't have 
1744                     for tx_hash, tx_height in hist:
1745                         if self.wallet.transactions.get(tx_hash) is None:
1746                             if (tx_hash, tx_height) not in requested_tx and (tx_hash, tx_height) not in missing_tx:
1747                                 missing_tx.append( (tx_hash, tx_height) )
1748
1749             elif method == 'blockchain.transaction.get':
1750                 tx_hash = params[0]
1751                 tx_height = params[1]
1752                 assert tx_hash == hash_encode(Hash(result.decode('hex')))
1753                 tx = Transaction(result)
1754                 self.wallet.receive_tx_callback(tx_hash, tx, tx_height)
1755                 self.was_updated = True
1756                 requested_tx.remove( (tx_hash, tx_height) )
1757                 print_error("received tx:", tx_hash, len(tx.raw))
1758
1759             else:
1760                 print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
1761
1762             if self.was_updated and not requested_tx:
1763                 self.wallet.network.trigger_callback('updated')
1764                 self.wallet.network.trigger_callback("new_transaction") # Updated gets called too many times from other places as well; if we use that signal we get the notification three times
1765
1766                 self.was_updated = False