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