new account creation method (gui)
[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     def verify_message(self, address, signature, message):
677         try:
678             EC_KEY.verify_message(address, signature, message)
679             return True
680         except BaseException as e:
681             print_error("Verification error: {0}".format(e))
682             return False
683
684
685     def change_gap_limit(self, value):
686         if value >= self.gap_limit:
687             self.gap_limit = value
688             self.storage.put('gap_limit', self.gap_limit, True)
689             #self.interface.poke('synchronizer')
690             return True
691
692         elif value >= self.min_acceptable_gap():
693             for key, account in self.accounts.items():
694                 addresses = account[0]
695                 k = self.num_unused_trailing_addresses(addresses)
696                 n = len(addresses) - k + value
697                 addresses = addresses[0:n]
698                 self.accounts[key][0] = addresses
699
700             self.gap_limit = value
701             self.storage.put('gap_limit', self.gap_limit, True)
702             self.save_accounts()
703             return True
704         else:
705             return False
706
707     def num_unused_trailing_addresses(self, addresses):
708         k = 0
709         for a in addresses[::-1]:
710             if self.history.get(a):break
711             k = k + 1
712         return k
713
714     def min_acceptable_gap(self):
715         # fixme: this assumes wallet is synchronized
716         n = 0
717         nmax = 0
718
719         for account in self.accounts.values():
720             addresses = account.get_addresses(0)
721             k = self.num_unused_trailing_addresses(addresses)
722             for a in addresses[0:-k]:
723                 if self.history.get(a):
724                     n = 0
725                 else:
726                     n += 1
727                     if n > nmax: nmax = n
728         return nmax + 1
729
730
731     def address_is_old(self, address):
732         age = -1
733         h = self.history.get(address, [])
734         if h == ['*']:
735             return True
736         for tx_hash, tx_height in h:
737             if tx_height == 0:
738                 tx_age = 0
739             else: 
740                 tx_age = self.verifier.blockchain.height - tx_height + 1
741             if tx_age > age:
742                 age = tx_age
743         return age > 2
744
745
746     def synchronize_sequence(self, account, for_change):
747         limit = self.gap_limit_for_change if for_change else self.gap_limit
748         new_addresses = []
749         while True:
750             addresses = account.get_addresses(for_change)
751             if len(addresses) < limit:
752                 address = account.create_new_address(for_change)
753                 self.history[address] = []
754                 new_addresses.append( address )
755                 continue
756
757             if map( lambda a: self.address_is_old(a), addresses[-limit:] ) == limit*[False]:
758                 break
759             else:
760                 address = account.create_new_address(for_change)
761                 self.history[address] = []
762                 new_addresses.append( address )
763
764         return new_addresses
765         
766
767
768     def create_pending_accounts(self):
769         for account_type in ['1','2of2','2of3']:
770             if not self.has_master_public_keys(account_type):
771                 continue
772             k, a = self.new_account_address(account_type)
773             if self.address_is_old(a):
774                 print_error( "creating account", a )
775                 self.create_account(account_type)
776
777
778     def synchronize_account(self, account):
779         new = []
780         new += self.synchronize_sequence(account, 0)
781         new += self.synchronize_sequence(account, 1)
782         return new
783
784
785     def synchronize(self):
786         if self.master_public_keys:
787             self.create_pending_accounts()
788         new = []
789         for account in self.accounts.values():
790             new += self.synchronize_account(account)
791         if new:
792             self.save_accounts()
793             self.storage.put('addr_history', self.history, True)
794         return new
795
796
797     def is_found(self):
798         return self.history.values() != [[]] * len(self.history) 
799
800
801     def add_contact(self, address, label=None):
802         self.addressbook.append(address)
803         self.storage.put('contacts', self.addressbook, True)
804         if label:  
805             self.set_label(address, label)
806
807
808     def delete_contact(self, addr):
809         if addr in self.addressbook:
810             self.addressbook.remove(addr)
811             self.storage.put('addressbook', self.addressbook, True)
812
813
814     def fill_addressbook(self):
815         for tx_hash, tx in self.transactions.items():
816             is_relevant, is_send, _, _ = self.get_tx_value(tx)
817             if is_send:
818                 for addr, v in tx.outputs:
819                     if not self.is_mine(addr) and addr not in self.addressbook:
820                         self.addressbook.append(addr)
821         # redo labels
822         # self.update_tx_labels()
823
824     def get_num_tx(self, address):
825         n = 0 
826         for tx in self.transactions.values():
827             if address in map(lambda x:x[0], tx.outputs): n += 1
828         return n
829
830
831     def get_address_flags(self, addr):
832         flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" 
833         flags += "F" if addr in self.frozen_addresses else "P" if addr in self.prioritized_addresses else "-"
834         return flags
835         
836
837     def get_tx_value(self, tx, account=None):
838         domain = self.get_account_addresses(account)
839         return tx.get_value(domain, self.prevout_values)
840
841     
842     def update_tx_outputs(self, tx_hash):
843         tx = self.transactions.get(tx_hash)
844
845         for i, (addr, value) in enumerate(tx.outputs):
846             key = tx_hash+ ':%d'%i
847             self.prevout_values[key] = value
848
849         for item in tx.inputs:
850             if self.is_mine(item.get('address')):
851                 key = item['prevout_hash'] + ':%d'%item['prevout_n']
852                 self.spent_outputs.append(key)
853
854
855     def get_addr_balance(self, address):
856         assert self.is_mine(address)
857         h = self.history.get(address,[])
858         if h == ['*']: return 0,0
859         c = u = 0
860         received_coins = []   # list of coins received at address
861
862         for tx_hash, tx_height in h:
863             tx = self.transactions.get(tx_hash)
864             if not tx: continue
865
866             for i, (addr, value) in enumerate(tx.outputs):
867                 if addr == address:
868                     key = tx_hash + ':%d'%i
869                     received_coins.append(key)
870
871         for tx_hash, tx_height in h:
872             tx = self.transactions.get(tx_hash)
873             if not tx: continue
874             v = 0
875
876             for item in tx.inputs:
877                 addr = item.get('address')
878                 if addr == address:
879                     key = item['prevout_hash']  + ':%d'%item['prevout_n']
880                     value = self.prevout_values.get( key )
881                     if key in received_coins: 
882                         v -= value
883
884             for i, (addr, value) in enumerate(tx.outputs):
885                 key = tx_hash + ':%d'%i
886                 if addr == address:
887                     v += value
888
889             if tx_height:
890                 c += v
891             else:
892                 u += v
893         return c, u
894
895
896     def get_account_name(self, k):
897         if k == 0:
898             if self.seed_version == 4: 
899                 name = 'Main account'
900             else:
901                 name = 'Old account'
902         else:
903             default = "Unnamed account"
904             m = re.match("m/0'/(\d+)", k)
905             if m:
906                 num = m.group(1)
907                 if num == '0':
908                     default = "Main account"
909                 else:
910                     default = "Account %s"%num
911                     
912             m = re.match("m/1'/(\d+) & m/2'/(\d+)", k)
913             if m:
914                 num = m.group(1)
915                 default = "2of2 account %s"%num
916             name = self.labels.get(k, default)
917
918         return name
919
920     def get_account_names(self):
921         accounts = {}
922         for k, account in self.accounts.items():
923             accounts[k] = self.get_account_name(k)
924         if self.imported_keys:
925             accounts[-1] = 'Imported keys'
926         return accounts
927
928     def get_account_addresses(self, a, include_change=True):
929         if a is None:
930             o = self.addresses(True)
931         elif a == -1:
932             o = self.imported_keys.keys()
933         else:
934             ac = self.accounts[a]
935             o = ac.get_addresses(0)
936             if include_change: o += ac.get_addresses(1)
937         return o
938
939     def get_imported_balance(self):
940         cc = uu = 0
941         for addr in self.imported_keys.keys():
942             c, u = self.get_addr_balance(addr)
943             cc += c
944             uu += u
945         return cc, uu
946
947     def get_account_balance(self, account):
948         if account is None:
949             return self.get_balance()
950         elif account == -1:
951             return self.get_imported_balance()
952         
953         conf = unconf = 0
954         for addr in self.get_account_addresses(account): 
955             c, u = self.get_addr_balance(addr)
956             conf += c
957             unconf += u
958         return conf, unconf
959
960     def get_frozen_balance(self):
961         conf = unconf = 0
962         for addr in self.frozen_addresses:
963             c, u = self.get_addr_balance(addr)
964             conf += c
965             unconf += u
966         return conf, unconf
967
968         
969     def get_balance(self):
970         cc = uu = 0
971         for a in self.accounts.keys():
972             c, u = self.get_account_balance(a)
973             cc += c
974             uu += u
975         c, u = self.get_imported_balance()
976         cc += c
977         uu += u
978         return cc, uu
979
980
981     def get_unspent_coins(self, domain=None):
982         coins = []
983         if domain is None: domain = self.addresses(True)
984         for addr in domain:
985             h = self.history.get(addr, [])
986             if h == ['*']: continue
987             for tx_hash, tx_height in h:
988                 tx = self.transactions.get(tx_hash)
989                 if tx is None: raise BaseException("Wallet not synchronized")
990                 for output in tx.d.get('outputs'):
991                     if output.get('address') != addr: continue
992                     key = tx_hash + ":%d" % output.get('prevout_n')
993                     if key in self.spent_outputs: continue
994                     output['prevout_hash'] = tx_hash
995                     output['height'] = tx_height
996                     coins.append((tx_height, output))
997
998         # sort by age
999         if coins:
1000             coins = sorted(coins)
1001             if coins[-1][0] != 0:
1002                 while coins[0][0] == 0: 
1003                     coins = coins[1:] + [ coins[0] ]
1004         return [x[1] for x in coins]
1005
1006
1007
1008     def choose_tx_inputs( self, amount, fixed_fee, domain = None ):
1009         """ todo: minimize tx size """
1010         total = 0
1011         fee = self.fee if fixed_fee is None else fixed_fee
1012         if domain is None:
1013             domain = self.addresses()
1014
1015         for i in self.frozen_addresses:
1016             if i in domain: domain.remove(i)
1017
1018         prioritized = []
1019         for i in self.prioritized_addresses:
1020             if i in domain:
1021                 domain.remove(i)
1022                 prioritized.append(i)
1023
1024         coins = self.get_unspent_coins(domain)
1025         prioritized_coins = self.get_unspent_coins(prioritized)
1026
1027         inputs = []
1028         coins = prioritized_coins + coins
1029
1030         for item in coins: 
1031             addr = item.get('address')
1032             v = item.get('value')
1033             total += v
1034             inputs.append(item)
1035             fee = self.estimated_fee(inputs) if fixed_fee is None else fixed_fee
1036             if total >= amount + fee: break
1037         else:
1038             inputs = []
1039
1040         return inputs, total, fee
1041
1042
1043     def set_fee(self, fee):
1044         if self.fee != fee:
1045             self.fee = fee
1046             self.storage.put('fee_per_kb', self.fee, True)
1047         
1048     def estimated_fee(self, inputs):
1049         estimated_size =  len(inputs) * 180 + 80     # this assumes non-compressed keys
1050         fee = self.fee * int(round(estimated_size/1024.))
1051         if fee == 0: fee = self.fee
1052         return fee
1053
1054
1055     def add_tx_change( self, inputs, outputs, amount, fee, total, change_addr=None):
1056         "add change to a transaction"
1057         change_amount = total - ( amount + fee )
1058         if change_amount != 0:
1059             if not change_addr:
1060
1061                 # send change to one of the accounts involved in the tx
1062                 address = inputs[0].get('address')
1063                 account, _ = self.get_address_index(address)
1064
1065                 if not self.use_change or account == -1:
1066                     change_addr = inputs[-1]['address']
1067                 else:
1068                     change_addr = self.accounts[account].get_addresses(1)[-self.gap_limit_for_change]
1069
1070             # Insert the change output at a random position in the outputs
1071             posn = random.randint(0, len(outputs))
1072             outputs[posn:posn] = [( change_addr,  change_amount)]
1073         return outputs
1074
1075
1076     def get_history(self, address):
1077         with self.lock:
1078             return self.history.get(address)
1079
1080
1081     def get_status(self, h):
1082         if not h: return None
1083         if h == ['*']: return '*'
1084         status = ''
1085         for tx_hash, height in h:
1086             status += tx_hash + ':%d:' % height
1087         return hashlib.sha256( status ).digest().encode('hex')
1088
1089
1090     def receive_tx_callback(self, tx_hash, tx, tx_height):
1091
1092         with self.transaction_lock:
1093             self.add_extra_addresses(tx)
1094             if not self.check_new_tx(tx_hash, tx):
1095                 # may happen due to pruning
1096                 print_error("received transaction that is no longer referenced in history", tx_hash)
1097                 return
1098             self.transactions[tx_hash] = tx
1099             self.network.interface.pending_transactions_for_notifications.append(tx)
1100             self.save_transactions()
1101             if self.verifier and tx_height>0: 
1102                 self.verifier.add(tx_hash, tx_height)
1103             self.update_tx_outputs(tx_hash)
1104
1105
1106     def save_transactions(self):
1107         tx = {}
1108         for k,v in self.transactions.items():
1109             tx[k] = str(v)
1110         self.storage.put('transactions', tx, True)
1111
1112     def receive_history_callback(self, addr, hist):
1113
1114         if not self.check_new_history(addr, hist):
1115             raise BaseException("error: received history for %s is not consistent with known transactions"%addr)
1116             
1117         with self.lock:
1118             self.history[addr] = hist
1119             self.storage.put('addr_history', self.history, True)
1120
1121         if hist != ['*']:
1122             for tx_hash, tx_height in hist:
1123                 if tx_height>0:
1124                     # add it in case it was previously unconfirmed
1125                     if self.verifier: self.verifier.add(tx_hash, tx_height)
1126
1127
1128     def get_tx_history(self, account=None):
1129         with self.transaction_lock:
1130             history = self.transactions.items()
1131             history.sort(key = lambda x: self.verifier.get_txpos(x[0]))
1132             result = []
1133     
1134             balance = 0
1135             for tx_hash, tx in history:
1136                 is_relevant, is_mine, v, fee = self.get_tx_value(tx, account)
1137                 if v is not None: balance += v
1138
1139             c, u = self.get_account_balance(account)
1140
1141             if balance != c+u:
1142                 result.append( ('', 1000, 0, c+u-balance, None, c+u-balance, None ) )
1143
1144             balance = c + u - balance
1145             for tx_hash, tx in history:
1146                 is_relevant, is_mine, value, fee = self.get_tx_value(tx, account)
1147                 if not is_relevant:
1148                     continue
1149                 if value is not None:
1150                     balance += value
1151
1152                 conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None)
1153                 result.append( (tx_hash, conf, is_mine, value, fee, balance, timestamp) )
1154
1155         return result
1156
1157
1158     def get_label(self, tx_hash):
1159         label = self.labels.get(tx_hash)
1160         is_default = (label == '') or (label is None)
1161         if is_default: label = self.get_default_label(tx_hash)
1162         return label, is_default
1163
1164
1165     def get_default_label(self, tx_hash):
1166         tx = self.transactions.get(tx_hash)
1167         default_label = ''
1168         if tx:
1169             is_relevant, is_mine, _, _ = self.get_tx_value(tx)
1170             if is_mine:
1171                 for o in tx.outputs:
1172                     o_addr, _ = o
1173                     if not self.is_mine(o_addr):
1174                         try:
1175                             default_label = self.labels[o_addr]
1176                         except KeyError:
1177                             default_label = o_addr
1178                         break
1179                 else:
1180                     default_label = '(internal)'
1181             else:
1182                 for o in tx.outputs:
1183                     o_addr, _ = o
1184                     if self.is_mine(o_addr) and not self.is_change(o_addr):
1185                         break
1186                 else:
1187                     for o in tx.outputs:
1188                         o_addr, _ = o
1189                         if self.is_mine(o_addr):
1190                             break
1191                     else:
1192                         o_addr = None
1193
1194                 if o_addr:
1195                     dest_label = self.labels.get(o_addr)
1196                     try:
1197                         default_label = self.labels[o_addr]
1198                     except KeyError:
1199                         default_label = o_addr
1200
1201         return default_label
1202
1203
1204     def make_unsigned_transaction(self, outputs, fee=None, change_addr=None, domain=None ):
1205         for address, x in outputs:
1206             assert is_valid(address)
1207         amount = sum( map(lambda x:x[1], outputs) )
1208         inputs, total, fee = self.choose_tx_inputs( amount, fee, domain )
1209         if not inputs:
1210             raise ValueError("Not enough funds")
1211         self.add_input_info(inputs)
1212         outputs = self.add_tx_change(inputs, outputs, amount, fee, total, change_addr)
1213         return Transaction.from_io(inputs, outputs)
1214
1215
1216     def mktx_from_account(self, outputs, password, fee=None, change_addr=None, account=None):
1217         domain = self.get_account_addresses(account) if account else None
1218         return self.mktx(outputs, password, fee, change_addr, domain)
1219
1220
1221     def mktx(self, outputs, password, fee=None, change_addr=None, domain= None ):
1222         tx = self.make_unsigned_transaction(outputs, fee, change_addr, domain)
1223         keypairs = {}
1224         self.add_keypairs_from_wallet(tx, keypairs, password)
1225         self.sign_transaction(tx, keypairs)
1226         return tx
1227
1228
1229     def add_input_info(self, inputs):
1230         for txin in inputs:
1231             address = txin['address']
1232             account, sequence = self.get_address_index(address)
1233             txin['KeyID'] = self.get_keyID(account, sequence)
1234             redeemScript = self.accounts[account].redeem_script(sequence)
1235             if redeemScript: 
1236                 txin['redeemScript'] = redeemScript
1237             else:
1238                 txin['redeemPubkey'] = self.accounts[account].get_pubkey(*sequence)
1239
1240
1241     def sign_transaction(self, tx, keypairs):
1242         tx.sign(keypairs)
1243         run_hook('sign_transaction', tx)
1244
1245
1246     def sendtx(self, tx):
1247         # synchronous
1248         h = self.send_tx(tx)
1249         self.tx_event.wait()
1250         return self.receive_tx(h)
1251
1252     def send_tx(self, tx):
1253         # asynchronous
1254         self.tx_event.clear()
1255         self.network.interface.send([('blockchain.transaction.broadcast', [str(tx)])], self.on_broadcast)
1256         return tx.hash()
1257
1258     def on_broadcast(self, i, r):
1259         self.tx_result = r.get('result')
1260         self.tx_event.set()
1261
1262     def receive_tx(self,tx_hash):
1263         out = self.tx_result 
1264         if out != tx_hash:
1265             return False, "error: " + out
1266         return True, out
1267
1268
1269
1270     def update_password(self, seed, old_password, new_password):
1271         if new_password == '': new_password = None
1272         # this will throw an exception if unicode cannot be converted
1273         self.seed = pw_encode( seed, new_password)
1274         self.storage.put('seed', self.seed, True)
1275         self.use_encryption = (new_password != None)
1276         self.storage.put('use_encryption', self.use_encryption,True)
1277         for k in self.imported_keys.keys():
1278             a = self.imported_keys[k]
1279             b = pw_decode(a, old_password)
1280             c = pw_encode(b, new_password)
1281             self.imported_keys[k] = c
1282         self.storage.put('imported_keys', self.imported_keys, True)
1283
1284         for k, v in self.master_private_keys.items():
1285             b = pw_decode(v, old_password)
1286             c = pw_encode(b, new_password)
1287             self.master_private_keys[k] = c
1288         self.storage.put('master_private_keys', self.master_private_keys, True)
1289
1290
1291     def freeze(self,addr):
1292         if self.is_mine(addr) and addr not in self.frozen_addresses:
1293             self.unprioritize(addr)
1294             self.frozen_addresses.append(addr)
1295             self.storage.put('frozen_addresses', self.frozen_addresses, True)
1296             return True
1297         else:
1298             return False
1299
1300     def unfreeze(self,addr):
1301         if self.is_mine(addr) and addr in self.frozen_addresses:
1302             self.frozen_addresses.remove(addr)
1303             self.storage.put('frozen_addresses', self.frozen_addresses, True)
1304             return True
1305         else:
1306             return False
1307
1308     def prioritize(self,addr):
1309         if self.is_mine(addr) and addr not in self.prioritized_addresses:
1310             self.unfreeze(addr)
1311             self.prioritized_addresses.append(addr)
1312             self.storage.put('prioritized_addresses', self.prioritized_addresses, True)
1313             return True
1314         else:
1315             return False
1316
1317     def unprioritize(self,addr):
1318         if self.is_mine(addr) and addr in self.prioritized_addresses:
1319             self.prioritized_addresses.remove(addr)
1320             self.storage.put('prioritized_addresses', self.prioritized_addresses, True)
1321             return True
1322         else:
1323             return False
1324
1325
1326     def set_verifier(self, verifier):
1327         self.verifier = verifier
1328
1329         # review transactions that are in the history
1330         for addr, hist in self.history.items():
1331             if hist == ['*']: continue
1332             for tx_hash, tx_height in hist:
1333                 if tx_height>0:
1334                     # add it in case it was previously unconfirmed
1335                     self.verifier.add(tx_hash, tx_height)
1336
1337
1338         # if we are on a pruning server, remove unverified transactions
1339         vr = self.verifier.transactions.keys() + self.verifier.verified_tx.keys()
1340         for tx_hash in self.transactions.keys():
1341             if tx_hash not in vr:
1342                 self.transactions.pop(tx_hash)
1343
1344
1345
1346     def check_new_history(self, addr, hist):
1347         
1348         # check that all tx in hist are relevant
1349         if hist != ['*']:
1350             for tx_hash, height in hist:
1351                 tx = self.transactions.get(tx_hash)
1352                 if not tx: continue
1353                 if not tx.has_address(addr):
1354                     return False
1355
1356         # check that we are not "orphaning" a transaction
1357         old_hist = self.history.get(addr,[])
1358         if old_hist == ['*']: return True
1359
1360         for tx_hash, height in old_hist:
1361             if tx_hash in map(lambda x:x[0], hist): continue
1362             found = False
1363             for _addr, _hist in self.history.items():
1364                 if _addr == addr: continue
1365                 if _hist == ['*']: continue
1366                 _tx_hist = map(lambda x:x[0], _hist)
1367                 if tx_hash in _tx_hist:
1368                     found = True
1369                     break
1370
1371             if not found:
1372                 tx = self.transactions.get(tx_hash)
1373                 # tx might not be there
1374                 if not tx: continue
1375                 
1376                 # already verified?
1377                 if self.verifier.get_height(tx_hash):
1378                     continue
1379                 # unconfirmed tx
1380                 print_error("new history is orphaning transaction:", tx_hash)
1381                 # check that all outputs are not mine, request histories
1382                 ext_requests = []
1383                 for _addr, _v in tx.outputs:
1384                     # assert not self.is_mine(_addr)
1385                     ext_requests.append( ('blockchain.address.get_history', [_addr]) )
1386
1387                 ext_h = self.network.synchronous_get(ext_requests)
1388                 print_error("sync:", ext_requests, ext_h)
1389                 height = None
1390                 for h in ext_h:
1391                     if h == ['*']: continue
1392                     for item in h:
1393                         if item.get('tx_hash') == tx_hash:
1394                             height = item.get('height')
1395                 if height:
1396                     print_error("found height for", tx_hash, height)
1397                     self.verifier.add(tx_hash, height)
1398                 else:
1399                     print_error("removing orphaned tx from history", tx_hash)
1400                     self.transactions.pop(tx_hash)
1401
1402         return True
1403
1404
1405
1406     def check_new_tx(self, tx_hash, tx):
1407         # 1 check that tx is referenced in addr_history. 
1408         addresses = []
1409         for addr, hist in self.history.items():
1410             if hist == ['*']:continue
1411             for txh, height in hist:
1412                 if txh == tx_hash: 
1413                     addresses.append(addr)
1414
1415         if not addresses:
1416             return False
1417
1418         # 2 check that referencing addresses are in the tx
1419         for addr in addresses:
1420             if not tx.has_address(addr):
1421                 return False
1422
1423         return True
1424
1425
1426     def start_threads(self, network):
1427         from verifier import TxVerifier
1428         self.network = network
1429         self.verifier = TxVerifier(self.network, self.storage)
1430         self.verifier.start()
1431         self.set_verifier(self.verifier)
1432         self.synchronizer = WalletSynchronizer(self)
1433         self.synchronizer.start()
1434
1435     def stop_threads(self):
1436         self.verifier.stop()
1437         self.synchronizer.stop()
1438
1439
1440
1441     def restore(self, callback):
1442         from i18n import _
1443         def wait_for_wallet():
1444             self.set_up_to_date(False)
1445             while not self.is_up_to_date():
1446                 msg = "%s\n%s %d\n%s %.1f"%(
1447                     _("Please wait..."),
1448                     _("Addresses generated:"),
1449                     len(self.addresses(True)),_("Kilobytes received:"), 
1450                     self.network.interface.bytes_received/1024.)
1451
1452                 apply(callback, (msg,))
1453                 time.sleep(0.1)
1454
1455         def wait_for_network():
1456             while not self.network.interface.is_connected:
1457                 msg = "%s \n" % (_("Connecting..."))
1458                 apply(callback, (msg,))
1459                 time.sleep(0.1)
1460
1461         # wait until we are connected, because the user might have selected another server
1462         wait_for_network()
1463
1464         # try to restore old account
1465         self.create_old_account()
1466         wait_for_wallet()
1467
1468         if self.is_found():
1469             self.seed_version = 4
1470             self.storage.put('seed_version', wallet.seed_version, True)
1471         else:
1472             self.accounts.pop(0)
1473             self.create_accounts()
1474             wait_for_wallet()
1475
1476
1477
1478
1479 class WalletSynchronizer(threading.Thread):
1480
1481
1482     def __init__(self, wallet):
1483         threading.Thread.__init__(self)
1484         self.daemon = True
1485         self.wallet = wallet
1486         wallet.synchronizer = self
1487         self.network = self.wallet.network
1488         #self.wallet.network.register_callback('connected', lambda: self.wallet.set_up_to_date(False))
1489         self.was_updated = True
1490         self.running = False
1491         self.lock = threading.Lock()
1492         self.queue = Queue.Queue()
1493
1494     def stop(self):
1495         with self.lock: self.running = False
1496
1497     def is_running(self):
1498         with self.lock: return self.running
1499
1500     
1501     def subscribe_to_addresses(self, addresses):
1502         messages = []
1503         for addr in addresses:
1504             messages.append(('blockchain.address.subscribe', [addr]))
1505         self.network.subscribe( messages, lambda i,r: self.queue.put(r))
1506
1507
1508     def run(self):
1509         with self.lock:
1510             self.running = True
1511
1512         while self.is_running():
1513             
1514             if not self.network.is_connected():
1515                 print_error("synchronizer: waiting for interface")
1516                 self.network.wait_until_connected()
1517                 
1518             self.run_interface(self.network.interface)
1519
1520
1521     def run_interface(self, interface):
1522
1523         print_error("synchronizer: connected to", interface.server)
1524
1525         requested_tx = []
1526         missing_tx = []
1527         requested_histories = {}
1528
1529         # request any missing transactions
1530         for history in self.wallet.history.values():
1531             if history == ['*']: continue
1532             for tx_hash, tx_height in history:
1533                 if self.wallet.transactions.get(tx_hash) is None and (tx_hash, tx_height) not in missing_tx:
1534                     missing_tx.append( (tx_hash, tx_height) )
1535
1536         if missing_tx:
1537             print_error("missing tx", missing_tx)
1538
1539         # subscriptions
1540         self.subscribe_to_addresses(self.wallet.addresses(True, next=True))
1541
1542         while self.is_running():
1543             # 1. create new addresses
1544             new_addresses = self.wallet.synchronize()
1545
1546             # request missing addresses
1547             if new_addresses:
1548                 self.subscribe_to_addresses(new_addresses)
1549
1550             # request missing transactions
1551             for tx_hash, tx_height in missing_tx:
1552                 if (tx_hash, tx_height) not in requested_tx:
1553                     interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], lambda i,r: self.queue.put(r))
1554                     requested_tx.append( (tx_hash, tx_height) )
1555             missing_tx = []
1556
1557             # detect if situation has changed
1558             if interface.is_up_to_date() and self.queue.empty():
1559                 if not self.wallet.is_up_to_date():
1560                     self.wallet.set_up_to_date(True)
1561                     self.was_updated = True
1562             else:
1563                 if self.wallet.is_up_to_date():
1564                     self.wallet.set_up_to_date(False)
1565                     self.was_updated = True
1566
1567             if self.was_updated:
1568                 self.wallet.network.trigger_callback('updated')
1569                 self.was_updated = False
1570
1571             # 2. get a response
1572             try:
1573                 r = self.queue.get(block=True, timeout=1)
1574             except Queue.Empty:
1575                 continue
1576
1577             if interface != self.network.interface:
1578                 break
1579             
1580             if not r:
1581                 continue
1582
1583             # 3. handle response
1584             method = r['method']
1585             params = r['params']
1586             result = r.get('result')
1587             error = r.get('error')
1588             if error:
1589                 print "error", r
1590                 continue
1591
1592             if method == 'blockchain.address.subscribe':
1593                 addr = params[0]
1594                 if self.wallet.get_status(self.wallet.get_history(addr)) != result:
1595                     if requested_histories.get(addr) is None:
1596                         interface.send([('blockchain.address.get_history', [addr])], lambda i,r:self.queue.put(r))
1597                         requested_histories[addr] = result
1598
1599             elif method == 'blockchain.address.get_history':
1600                 addr = params[0]
1601                 print_error("receiving history", addr, result)
1602                 if result == ['*']:
1603                     assert requested_histories.pop(addr) == '*'
1604                     self.wallet.receive_history_callback(addr, result)
1605                 else:
1606                     hist = []
1607                     # check that txids are unique
1608                     txids = []
1609                     for item in result:
1610                         tx_hash = item['tx_hash']
1611                         if tx_hash not in txids:
1612                             txids.append(tx_hash)
1613                             hist.append( (tx_hash, item['height']) )
1614
1615                     if len(hist) != len(result):
1616                         raise BaseException("error: server sent history with non-unique txid", result)
1617
1618                     # check that the status corresponds to what was announced
1619                     rs = requested_histories.pop(addr)
1620                     if self.wallet.get_status(hist) != rs:
1621                         raise BaseException("error: status mismatch: %s"%addr)
1622                 
1623                     # store received history
1624                     self.wallet.receive_history_callback(addr, hist)
1625
1626                     # request transactions that we don't have 
1627                     for tx_hash, tx_height in hist:
1628                         if self.wallet.transactions.get(tx_hash) is None:
1629                             if (tx_hash, tx_height) not in requested_tx and (tx_hash, tx_height) not in missing_tx:
1630                                 missing_tx.append( (tx_hash, tx_height) )
1631
1632             elif method == 'blockchain.transaction.get':
1633                 tx_hash = params[0]
1634                 tx_height = params[1]
1635                 assert tx_hash == hash_encode(Hash(result.decode('hex')))
1636                 tx = Transaction(result)
1637                 self.wallet.receive_tx_callback(tx_hash, tx, tx_height)
1638                 self.was_updated = True
1639                 requested_tx.remove( (tx_hash, tx_height) )
1640                 print_error("received tx:", tx_hash, len(tx.raw))
1641
1642             else:
1643                 print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
1644
1645             if self.was_updated and not requested_tx:
1646                 self.wallet.network.trigger_callback('updated')
1647                 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
1648
1649                 self.was_updated = False