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