755f063cf4e38470a274eeae8e4f2efcd0d97340
[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, user_dir, format_satoshis
34 from bitcoin import *
35
36
37 # AES encryption
38 EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
39 DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e))
40
41 def pw_encode(s, password):
42     if password:
43         secret = Hash(password)
44         return EncodeAES(secret, s)
45     else:
46         return s
47
48 def pw_decode(s, password):
49     if password is not None:
50         secret = Hash(password)
51         try:
52             d = DecodeAES(secret, s)
53         except:
54             raise BaseException('Invalid password')
55         return d
56     else:
57         return s
58
59
60
61
62
63 from version import ELECTRUM_VERSION, SEED_VERSION
64
65
66 class Wallet:
67     def __init__(self, config={}):
68
69         self.config = config
70         self.electrum_version = ELECTRUM_VERSION
71         self.gap_limit_for_change = 3 # constant
72
73         # saved fields
74         self.seed_version          = config.get('seed_version', SEED_VERSION)
75         self.gap_limit             = config.get('gap_limit', 5)
76         self.use_change            = config.get('use_change',True)
77         self.fee                   = int(config.get('fee_per_kb',20000))
78         self.num_zeros             = int(config.get('num_zeros',0))
79         self.use_encryption        = config.get('use_encryption', False)
80         self.seed                  = config.get('seed', '')               # encrypted
81         self.labels                = config.get('labels', {})
82         self.frozen_addresses      = config.get('frozen_addresses',[])
83         self.prioritized_addresses = config.get('prioritized_addresses',[])
84         self.addressbook           = config.get('contacts', [])
85         self.imported_keys         = config.get('imported_keys',{})
86         self.history               = config.get('addr_history',{})        # address -> list(txid, height)
87         self.accounts              = config.get('accounts', {})   # this should not include public keys
88
89         self.SequenceClass = ElectrumSequence
90         self.sequences = {}
91         self.sequences[0] = self.SequenceClass(self.config.get('master_public_key'))
92
93         if self.accounts.get(0) is None:
94             self.accounts[0] = { 0:[], 1:[], 'name':'Main account' }
95
96         self.transactions = {}
97         tx = config.get('transactions',{})
98         try:
99             for k,v in tx.items(): self.transactions[k] = Transaction(v)
100         except:
101             print_msg("Warning: Cannot deserialize transactions. skipping")
102         
103         # not saved
104         self.prevout_values = {}     # my own transaction outputs
105         self.spent_outputs = []
106
107         # spv
108         self.verifier = None
109
110         # there is a difference between wallet.up_to_date and interface.is_up_to_date()
111         # interface.is_up_to_date() returns true when all requests have been answered and processed
112         # wallet.up_to_date is true when the wallet is synchronized (stronger requirement)
113         
114         self.up_to_date = False
115         self.lock = threading.Lock()
116         self.transaction_lock = threading.Lock()
117         self.tx_event = threading.Event()
118
119         if self.seed_version != SEED_VERSION:
120             raise ValueError("This wallet seed is deprecated. Please run upgrade.py for a diagnostic.")
121
122         for tx_hash in self.transactions.keys():
123             self.update_tx_outputs(tx_hash)
124
125
126     def set_up_to_date(self,b):
127         with self.lock: self.up_to_date = b
128
129     def is_up_to_date(self):
130         with self.lock: return self.up_to_date
131
132     def update(self):
133         self.up_to_date = False
134         self.interface.poke('synchronizer')
135         while not self.is_up_to_date(): time.sleep(0.1)
136
137     def import_key(self, sec, password):
138         # check password
139         seed = self.decode_seed(password)
140         address = address_from_private_key(sec)
141
142         if self.is_mine(address):
143             raise BaseException('Address already in wallet')
144         
145         # store the originally requested keypair into the imported keys table
146         self.imported_keys[address] = pw_encode(sec, password )
147         return address
148         
149
150     def init_seed(self, seed):
151         if self.seed: raise BaseException("a seed exists")
152         if not seed: 
153             seed = random_seed(128)
154         self.seed = seed 
155         self.config.set_key('seed', self.seed, True)
156         self.config.set_key('seed_version', self.seed_version, True)
157         mpk = self.SequenceClass.mpk_from_seed(self.seed)
158         self.init_sequence(mpk)
159
160
161     def init_sequence(self, mpk):
162         self.config.set_key('master_public_key', mpk, True)
163         self.sequences[0] = self.SequenceClass(mpk)
164         self.accounts[0] = { 0:[], 1:[], 'name':'Main account' }
165         self.config.set_key('accounts', self.accounts, True)
166
167
168     def addresses(self, include_change = False):
169         o = self.imported_keys.keys()
170         for a in self.accounts.values():
171             o += a[0]
172             if include_change: o += a[1]
173         return o
174
175
176     def is_mine(self, address):
177         return address in self.addresses(True)
178
179     def is_change(self, address):
180         if not self.is_mine(address): return False
181         if address in self.imported_keys.keys(): return False
182         acct, s = self.get_address_index(address)
183         return s[0] == 1
184
185     def get_master_public_key(self):
186         return self.config.get("master_public_key")
187
188     def get_address_index(self, address):
189         if address in self.imported_keys.keys():
190             raise BaseException("imported key")
191         for account in self.accounts.keys():
192             for for_change in [0,1]:
193                 addresses = self.accounts[account][for_change]
194                 for addr in addresses:
195                     if address == addr:
196                         return account, (for_change, addresses.index(addr))
197         raise BaseException("not found")
198         
199
200     def get_public_key(self, address):
201         account, sequence = self.get_address_index(address)
202         return self.sequences[account].get_pubkey( sequence )
203
204
205     def decode_seed(self, password):
206         seed = pw_decode(self.seed, password)
207         self.sequences[0].check_seed(seed)
208         return seed
209         
210     def get_private_key(self, address, password):
211         return self.get_private_keys([address], password).get(address)
212
213     def get_private_keys(self, addresses, password):
214         if not self.seed: return {}
215         # decode seed in any case, in order to test the password
216         seed = self.decode_seed(password)
217         out = {}
218         l_sequences = []
219         l_addresses = []
220         for address in addresses:
221             if address in self.imported_keys.keys():
222                 out[address] = pw_decode( self.imported_keys[address], password )
223             else:
224                 account, sequence = self.get_address_index(address)
225                 if account == 0:
226                     l_sequences.append(sequence)
227                     l_addresses.append(address)
228
229         pk = self.sequences[0].get_private_keys(l_sequences, seed)
230         for i, address in enumerate(l_addresses): out[address] = pk[i]                     
231         return out
232
233
234     def signrawtransaction(self, tx, input_info, private_keys, password):
235         unspent_coins = self.get_unspent_coins()
236         seed = self.decode_seed(password)
237
238         # convert private_keys to dict 
239         pk = {}
240         for sec in private_keys:
241             address = address_from_private_key(sec)
242             pk[address] = sec
243         private_keys = pk
244
245         for txin in tx.inputs:
246             # convert to own format
247             txin['tx_hash'] = txin['prevout_hash']
248             txin['index'] = txin['prevout_n']
249
250             for item in input_info:
251                 if item.get('txid') == txin['tx_hash'] and item.get('vout') == txin['index']:
252                     txin['raw_output_script'] = item['scriptPubKey']
253                     txin['redeemScript'] = item.get('redeemScript')
254                     txin['KeyID'] = item.get('KeyID')
255                     break
256             else:
257                 for item in unspent_coins:
258                     if txin['tx_hash'] == item['tx_hash'] and txin['index'] == item['index']:
259                         txin['raw_output_script'] = item['raw_output_script']
260                         break
261                 else:
262                     # if neither, we might want to get it from the server..
263                     raise
264
265             # find the address:
266             if txin.get('KeyID'):
267                 account, name, sequence = txin.get('KeyID')
268                 if name != 'Electrum': continue
269                 sec = self.sequences[account].get_private_key(sequence, seed)
270                 addr = self.sequences[account].get_address(sequence)
271                 txin['address'] = addr
272                 private_keys[addr] = sec
273
274             elif txin.get("redeemScript"):
275                 txin['address'] = hash_160_to_bc_address(hash_160(txin.get("redeemScript").decode('hex')), 5)
276
277             elif txin.get("raw_output_script"):
278                 import deserialize
279                 addr = deserialize.get_address_from_output_script(txin.get("raw_output_script").decode('hex'))
280                 sec = self.get_private_key(addr, password)
281                 if sec: 
282                     private_keys[addr] = sec
283                     txin['address'] = addr
284
285         tx.sign( private_keys )
286
287     def sign_message(self, address, message, password):
288         sec = self.get_private_key(address, password)
289         key = regenerate_key(sec)
290         compressed = is_compressed(sec)
291         return key.sign_message(message, compressed, address)
292
293
294     def create_new_address(self, account, for_change):
295         addresses = self.accounts[account][for_change]
296         n = len(addresses)
297         address = self.get_new_address( account, for_change, n)
298         self.accounts[account][for_change].append(address)
299         self.history[address] = []
300         print_msg(address)
301         return address
302         
303
304     def get_new_address(self, account, for_change, n):
305         return self.sequences[account].get_address((for_change, n))
306         print address
307         return address
308
309     def change_gap_limit(self, value):
310         if value >= self.gap_limit:
311             self.gap_limit = value
312             self.save()
313             self.interface.poke('synchronizer')
314             return True
315
316         elif value >= self.min_acceptable_gap():
317             for key, account in self.accounts.items():
318                 addresses = account[0]
319                 k = self.num_unused_trailing_addresses(addresses)
320                 n = len(addresses) - k + value
321                 addresses = addresses[0:n]
322                 self.accounts[key][0] = addresses
323
324             self.gap_limit = value
325             self.save()
326             return True
327         else:
328             return False
329
330     def num_unused_trailing_addresses(self, addresses):
331         k = 0
332         for a in addresses[::-1]:
333             if self.history.get(a):break
334             k = k + 1
335         return k
336
337     def min_acceptable_gap(self):
338         # fixme: this assumes wallet is synchronized
339         n = 0
340         nmax = 0
341
342         for account in self.accounts.values():
343             addresses = account[0]
344             k = self.num_unused_trailing_addresses(addresses)
345             for a in addresses[0:-k]:
346                 if self.history.get(a):
347                     n = 0
348                 else:
349                     n += 1
350                     if n > nmax: nmax = n
351         return nmax + 1
352
353
354     def address_is_old(self, address):
355         age = -1
356         h = self.history.get(address, [])
357         if h == ['*']:
358             return True
359         for tx_hash, tx_height in h:
360             if tx_height == 0:
361                 tx_age = 0
362             else: 
363                 tx_age = self.verifier.height - tx_height + 1
364             if tx_age > age:
365                 age = tx_age
366         return age > 2
367
368
369     def synchronize_sequence(self, account, for_change):
370         limit = self.gap_limit_for_change if for_change else self.gap_limit
371         addresses = self.accounts[account][for_change]
372         new_addresses = []
373         while True:
374             if len(addresses) < limit:
375                 new_addresses.append( self.create_new_address(account, for_change) )
376                 continue
377             if map( lambda a: self.address_is_old(a), addresses[-limit:] ) == limit*[False]:
378                 break
379             else:
380                 new_addresses.append( self.create_new_address(account, for_change) )
381         return new_addresses
382         
383
384     def synchronize_account(self, account):
385         new = []
386         new += self.synchronize_sequence(account, 0)
387         new += self.synchronize_sequence(account, 1)
388         return new
389
390     def synchronize(self):
391         new = []
392         for account in self.accounts.keys():
393             new += self.synchronize_account(account)
394         return new
395
396
397     def is_found(self):
398         return self.history.values() != [[]] * len(self.history) 
399
400
401     def fill_addressbook(self):
402         for tx_hash, tx in self.transactions.items():
403             is_send, _, _ = self.get_tx_value(tx)
404             if is_send:
405                 for addr, v in tx.outputs:
406                     if not self.is_mine(addr) and addr not in self.addressbook:
407                         self.addressbook.append(addr)
408         # redo labels
409         # self.update_tx_labels()
410
411     def get_num_tx(self, address):
412         n = 0 
413         for tx in self.transactions.values():
414             if address in map(lambda x:x[0], tx.outputs): n += 1
415         return n
416
417
418     def get_address_flags(self, addr):
419         flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" 
420         flags += "F" if addr in self.frozen_addresses else "P" if addr in self.prioritized_addresses else "-"
421         return flags
422         
423
424     def get_tx_value(self, tx, addresses=None):
425         if addresses is None: addresses = self.addresses(True)
426         return tx.get_value(addresses, self.prevout_values)
427
428
429     
430     def update_tx_outputs(self, tx_hash):
431         tx = self.transactions.get(tx_hash)
432
433         for i, (addr, value) in enumerate(tx.outputs):
434             key = tx_hash+ ':%d'%i
435             self.prevout_values[key] = value
436
437         for item in tx.inputs:
438             if self.is_mine(item.get('address')):
439                 key = item['prevout_hash'] + ':%d'%item['prevout_n']
440                 self.spent_outputs.append(key)
441
442
443     def get_addr_balance(self, address):
444         assert self.is_mine(address)
445         h = self.history.get(address,[])
446         if h == ['*']: return 0,0
447         c = u = 0
448         received_coins = []   # list of coins received at address
449
450         for tx_hash, tx_height in h:
451             tx = self.transactions.get(tx_hash)
452             if not tx: continue
453
454             for i, (addr, value) in enumerate(tx.outputs):
455                 if addr == address:
456                     key = tx_hash + ':%d'%i
457                     received_coins.append(key)
458
459         for tx_hash, tx_height in h:
460             tx = self.transactions.get(tx_hash)
461             if not tx: continue
462             v = 0
463
464             for item in tx.inputs:
465                 addr = item.get('address')
466                 if addr == address:
467                     key = item['prevout_hash']  + ':%d'%item['prevout_n']
468                     value = self.prevout_values.get( key )
469                     if key in received_coins: 
470                         v -= value
471
472             for i, (addr, value) in enumerate(tx.outputs):
473                 key = tx_hash + ':%d'%i
474                 if addr == address:
475                     v += value
476
477             if tx_height:
478                 c += v
479             else:
480                 u += v
481         return c, u
482
483     def get_account_addresses(self, a):
484         ac = self.accounts[a]
485         return ac[0] + ac[1]
486
487     def get_imported_balance(self):
488         cc = uu = 0
489         for addr in self.imported_keys.keys():
490             c, u = self.get_addr_balance(addr)
491             cc += c
492             uu += u
493         return cc, uu
494
495     def get_account_balance(self, account):
496         conf = unconf = 0
497         for addr in self.get_account_addresses(account): 
498             c, u = self.get_addr_balance(addr)
499             conf += c
500             unconf += u
501         return conf, unconf
502
503     def get_balance(self):
504         cc = uu = 0
505         for a in self.accounts.keys():
506             c, u = self.get_account_balance(a)
507             cc += c
508             uu += u
509         c, u = self.get_imported_balance()
510         cc += c
511         uu += u
512         return cc, uu
513
514
515     def get_unspent_coins(self, domain=None):
516         coins = []
517         if domain is None: domain = self.addresses(True)
518         for addr in domain:
519             h = self.history.get(addr, [])
520             if h == ['*']: continue
521             for tx_hash, tx_height in h:
522                 tx = self.transactions.get(tx_hash)
523                 if tx is None: raise BaseException("Wallet not synchronized")
524                 for output in tx.d.get('outputs'):
525                     if output.get('address') != addr: continue
526                     key = tx_hash + ":%d" % output.get('index')
527                     if key in self.spent_outputs: continue
528                     output['tx_hash'] = tx_hash
529                     coins.append(output)
530         return coins
531
532
533
534     def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ):
535         """ todo: minimize tx size """
536         total = 0
537         fee = self.fee if fixed_fee is None else fixed_fee
538
539         coins = []
540         prioritized_coins = []
541         domain = [from_addr] if from_addr else self.addresses(True)
542         for i in self.frozen_addresses:
543             if i in domain: domain.remove(i)
544
545         for i in self.prioritized_addresses:
546             if i in domain: domain.remove(i)
547
548         coins = self.get_unspent_coins(domain)
549         prioritized_coins = self.get_unspent_coins(self.prioritized_addresses)
550
551         inputs = []
552         coins = prioritized_coins + coins
553
554         for item in coins: 
555             addr = item.get('address')
556             v = item.get('value')
557             total += v
558             inputs.append( item )
559             fee = self.estimated_fee(inputs) if fixed_fee is None else fixed_fee
560             if total >= amount + fee: break
561         else:
562             inputs = []
563
564         return inputs, total, fee
565
566
567     def estimated_fee(self, inputs):
568         estimated_size =  len(inputs) * 180 + 80     # this assumes non-compressed keys
569         fee = self.fee * int(round(estimated_size/1024.))
570         if fee == 0: fee = self.fee
571         return fee
572
573
574     def add_tx_change( self, outputs, amount, fee, total, change_addr=None ):
575         change_amount = total - ( amount + fee )
576         if change_amount != 0:
577             # normally, the update thread should ensure that the last change address is unused
578             if not change_addr:
579                 change_addresses = self.accounts[0][1]
580                 change_addr = change_addresses[-self.gap_limit_for_change]
581             # Insert the change output at a random position in the outputs
582             posn = random.randint(0, len(outputs))
583             outputs[posn:posn] = [( change_addr,  change_amount)]
584         return outputs
585
586
587     def get_history(self, address):
588         with self.lock:
589             return self.history.get(address)
590
591     def get_status(self, h):
592         if not h: return None
593         if h == ['*']: return '*'
594         status = ''
595         for tx_hash, height in h:
596             status += tx_hash + ':%d:' % height
597         return hashlib.sha256( status ).digest().encode('hex')
598
599
600
601     def receive_tx_callback(self, tx_hash, tx, tx_height):
602
603
604         if not self.check_new_tx(tx_hash, tx):
605             # may happen due to pruning
606             print_error("received transaction that is no longer referenced in history", tx_hash)
607             return
608
609         with self.transaction_lock:
610             self.transactions[tx_hash] = tx
611             if self.verifier and tx_height>0: 
612                 self.verifier.add(tx_hash, tx_height)
613             self.update_tx_outputs(tx_hash)
614
615         self.save()
616
617
618     def receive_history_callback(self, addr, hist):
619
620         if not self.check_new_history(addr, hist):
621             raise BaseException("error: received history for %s is not consistent with known transactions"%addr)
622             
623         with self.lock:
624             self.history[addr] = hist
625             self.save()
626
627         if hist != ['*']:
628             for tx_hash, tx_height in hist:
629                 if tx_height>0:
630                     # add it in case it was previously unconfirmed
631                     if self.verifier: self.verifier.add(tx_hash, tx_height)
632
633
634     def get_tx_history(self):
635         with self.transaction_lock:
636             history = self.transactions.items()
637             history.sort(key = lambda x: self.verifier.verified_tx.get(x[0]) if self.verifier.verified_tx.get(x[0]) else (1e12,0,0))
638             result = []
639     
640             balance = 0
641             for tx_hash, tx in history:
642                 is_mine, v, fee = self.get_tx_value(tx)
643                 if v is not None: balance += v
644             c, u = self.get_balance()
645
646             if balance != c+u:
647                 #v_str = format_satoshis( c+u - balance, True, self.num_zeros)
648                 result.append( ('', 1000, 0, c+u-balance, None, c+u-balance, None ) )
649
650             balance = c + u - balance
651             for tx_hash, tx in history:
652                 conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None)
653                 is_mine, value, fee = self.get_tx_value(tx)
654                 if value is not None:
655                     balance += value
656
657                 result.append( (tx_hash, conf, is_mine, value, fee, balance, timestamp) )
658
659         return result
660
661
662     def get_label(self, tx_hash):
663         label = self.labels.get(tx_hash)
664         is_default = (label == '') or (label is None)
665         if is_default: label = self.get_default_label(tx_hash)
666         return label, is_default
667
668
669     def get_default_label(self, tx_hash):
670         tx = self.transactions.get(tx_hash)
671         default_label = ''
672         if tx:
673             is_mine, _, _ = self.get_tx_value(tx)
674             if is_mine:
675                 for o in tx.outputs:
676                     o_addr, _ = o
677                     if not self.is_mine(o_addr):
678                         try:
679                             default_label = self.labels[o_addr]
680                         except KeyError:
681                             default_label = o_addr
682                         break
683                 else:
684                     default_label = '(internal)'
685             else:
686                 for o in tx.outputs:
687                     o_addr, _ = o
688                     if self.is_mine(o_addr) and not self.is_change(o_addr):
689                         break
690                 else:
691                     for o in tx.outputs:
692                         o_addr, _ = o
693                         if self.is_mine(o_addr):
694                             break
695                     else:
696                         o_addr = None
697
698                 if o_addr:
699                     dest_label = self.labels.get(o_addr)
700                     try:
701                         default_label = self.labels[o_addr]
702                     except KeyError:
703                         default_label = o_addr
704
705         return default_label
706
707
708     def mktx(self, outputs, password, fee=None, change_addr=None, from_addr= None):
709
710         for address, x in outputs:
711             assert is_valid(address)
712
713         amount = sum( map(lambda x:x[1], outputs) )
714         inputs, total, fee = self.choose_tx_inputs( amount, fee, from_addr )
715         if not inputs:
716             raise ValueError("Not enough funds")
717
718         if not self.use_change and not change_addr:
719             change_addr = inputs[-1]['address']
720             print_error( "Sending change to", change_addr )
721         outputs = self.add_tx_change(outputs, amount, fee, total, change_addr)
722
723         tx = Transaction.from_io(inputs, outputs)
724
725         pk_addresses = []
726         for i in range(len(tx.inputs)):
727             txin = tx.inputs[i]
728             address = txin['address']
729             if address in self.imported_keys.keys(): 
730                 pk_addresses.append(address)
731                 continue
732             account, sequence = self.get_address_index(address)
733             txin['KeyID'] = (account, 'Electrum', sequence) # used by the server to find the key
734             pk_addr, redeemScript = self.sequences[account].get_input_info(sequence)
735             if redeemScript: txin['redeemScript'] = redeemScript
736             pk_addresses.append(pk_addr)
737
738         # get all private keys at once.
739         if self.seed:
740             private_keys = self.get_private_keys(pk_addresses, password)
741             tx.sign(private_keys)
742
743         for address, x in outputs:
744             if address not in self.addressbook and not self.is_mine(address):
745                 self.addressbook.append(address)
746
747         return tx
748
749
750
751     def sendtx(self, tx):
752         # synchronous
753         h = self.send_tx(tx)
754         self.tx_event.wait()
755         return self.receive_tx(h)
756
757     def send_tx(self, tx):
758         # asynchronous
759         self.tx_event.clear()
760         self.interface.send([('blockchain.transaction.broadcast', [str(tx)])], 'synchronizer')
761         return tx.hash()
762
763     def receive_tx(self,tx_hash):
764         out = self.tx_result 
765         if out != tx_hash:
766             return False, "error: " + out
767         return True, out
768
769
770
771     def update_password(self, seed, old_password, new_password):
772         if new_password == '': new_password = None
773         self.use_encryption = (new_password != None)
774         self.seed = pw_encode( seed, new_password)
775         self.config.set_key('seed', self.seed, True)
776         for k in self.imported_keys.keys():
777             a = self.imported_keys[k]
778             b = pw_decode(a, old_password)
779             c = pw_encode(b, new_password)
780             self.imported_keys[k] = c
781         self.save()
782
783
784
785     def freeze(self,addr):
786         if self.is_mine(addr) and addr not in self.frozen_addresses:
787             self.unprioritize(addr)
788             self.frozen_addresses.append(addr)
789             self.config.set_key('frozen_addresses', self.frozen_addresses, True)
790             return True
791         else:
792             return False
793
794     def unfreeze(self,addr):
795         if self.is_mine(addr) and addr in self.frozen_addresses:
796             self.frozen_addresses.remove(addr)
797             self.config.set_key('frozen_addresses', self.frozen_addresses, True)
798             return True
799         else:
800             return False
801
802     def prioritize(self,addr):
803         if self.is_mine(addr) and addr not in self.prioritized_addresses:
804             self.unfreeze(addr)
805             self.prioritized_addresses.append(addr)
806             self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
807             return True
808         else:
809             return False
810
811     def unprioritize(self,addr):
812         if self.is_mine(addr) and addr in self.prioritized_addresses:
813             self.prioritized_addresses.remove(addr)
814             self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
815             return True
816         else:
817             return False
818
819     def save(self):
820         tx = {}
821         for k,v in self.transactions.items():
822             tx[k] = str(v)
823             
824         s = {
825             'use_encryption': self.use_encryption,
826             'use_change': self.use_change,
827             'fee_per_kb': self.fee,
828             'accounts': self.accounts,
829             'addr_history': self.history, 
830             'labels': self.labels,
831             'contacts': self.addressbook,
832             'imported_keys': self.imported_keys,
833             'num_zeros': self.num_zeros,
834             'frozen_addresses': self.frozen_addresses,
835             'prioritized_addresses': self.prioritized_addresses,
836             'gap_limit': self.gap_limit,
837             'transactions': tx,
838         }
839         for k, v in s.items():
840             self.config.set_key(k,v)
841         self.config.save()
842
843     def set_verifier(self, verifier):
844         self.verifier = verifier
845
846         # review transactions that are in the history
847         for addr, hist in self.history.items():
848             if hist == ['*']: continue
849             for tx_hash, tx_height in hist:
850                 if tx_height>0:
851                     # add it in case it was previously unconfirmed
852                     self.verifier.add(tx_hash, tx_height)
853
854
855         # if we are on a pruning server, remove unverified transactions
856         vr = self.verifier.transactions.keys() + self.verifier.verified_tx.keys()
857         for tx_hash in self.transactions.keys():
858             if tx_hash not in vr:
859                 self.transactions.pop(tx_hash)
860
861
862
863     def check_new_history(self, addr, hist):
864         
865         # check that all tx in hist are relevant
866         if hist != ['*']:
867             for tx_hash, height in hist:
868                 tx = self.transactions.get(tx_hash)
869                 if not tx: continue
870                 if not tx.has_address(addr):
871                     return False
872
873         # check that we are not "orphaning" a transaction
874         old_hist = self.history.get(addr,[])
875         if old_hist == ['*']: return True
876
877         for tx_hash, height in old_hist:
878             if tx_hash in map(lambda x:x[0], hist): continue
879             found = False
880             for _addr, _hist in self.history.items():
881                 if _addr == addr: continue
882                 if _hist == ['*']: continue
883                 _tx_hist = map(lambda x:x[0], _hist)
884                 if tx_hash in _tx_hist:
885                     found = True
886                     break
887
888             if not found:
889                 tx = self.transactions.get(tx_hash)
890                 # tx might not be there
891                 if not tx: continue
892                 
893                 # already verified?
894                 if self.verifier.get_height(tx_hash):
895                     continue
896                 # unconfirmed tx
897                 print_error("new history is orphaning transaction:", tx_hash)
898                 # check that all outputs are not mine, request histories
899                 ext_requests = []
900                 for _addr, _v in tx.outputs:
901                     # assert not self.is_mine(_addr)
902                     ext_requests.append( ('blockchain.address.get_history', [_addr]) )
903
904                 ext_h = self.interface.synchronous_get(ext_requests)
905                 print_error("sync:", ext_requests, ext_h)
906                 height = None
907                 for h in ext_h:
908                     if h == ['*']: continue
909                     for item in h:
910                         if item.get('tx_hash') == tx_hash:
911                             height = item.get('height')
912                 if height:
913                     print_error("found height for", tx_hash, height)
914                     self.verifier.add(tx_hash, height)
915                 else:
916                     print_error("removing orphaned tx from history", tx_hash)
917                     self.transactions.pop(tx_hash)
918
919         return True
920
921
922
923     def check_new_tx(self, tx_hash, tx):
924         # 1 check that tx is referenced in addr_history. 
925         addresses = []
926         for addr, hist in self.history.items():
927             if hist == ['*']:continue
928             for txh, height in hist:
929                 if txh == tx_hash: 
930                     addresses.append(addr)
931
932         if not addresses:
933             return False
934
935         # 2 check that referencing addresses are in the tx
936         for addr in addresses:
937             if not tx.has_address(addr):
938                 return False
939
940         return True
941
942
943
944
945 class WalletSynchronizer(threading.Thread):
946
947
948     def __init__(self, wallet, config):
949         threading.Thread.__init__(self)
950         self.daemon = True
951         self.wallet = wallet
952         self.interface = self.wallet.interface
953         self.interface.register_channel('synchronizer')
954         self.wallet.interface.register_callback('connected', lambda: self.wallet.set_up_to_date(False))
955         self.was_updated = True
956         self.running = False
957         self.lock = threading.Lock()
958
959     def stop(self):
960         with self.lock: self.running = False
961         self.interface.poke('synchronizer')
962
963     def is_running(self):
964         with self.lock: return self.running
965
966     
967     def subscribe_to_addresses(self, addresses):
968         messages = []
969         for addr in addresses:
970             messages.append(('blockchain.address.subscribe', [addr]))
971         self.interface.send( messages, 'synchronizer')
972
973
974     def run(self):
975         with self.lock: self.running = True
976
977         requested_tx = []
978         missing_tx = []
979         requested_histories = {}
980
981         # request any missing transactions
982         for history in self.wallet.history.values():
983             if history == ['*']: continue
984             for tx_hash, tx_height in history:
985                 if self.wallet.transactions.get(tx_hash) is None and (tx_hash, tx_height) not in missing_tx:
986                     missing_tx.append( (tx_hash, tx_height) )
987         print_error("missing tx", missing_tx)
988
989         # wait until we are connected, in case the user is not connected
990         while not self.interface.is_connected:
991             time.sleep(1)
992         
993         # subscriptions
994         self.subscribe_to_addresses(self.wallet.addresses(True))
995
996         while self.is_running():
997             # 1. create new addresses
998             new_addresses = self.wallet.synchronize()
999
1000             # request missing addresses
1001             if new_addresses:
1002                 self.subscribe_to_addresses(new_addresses)
1003
1004             # request missing transactions
1005             for tx_hash, tx_height in missing_tx:
1006                 if (tx_hash, tx_height) not in requested_tx:
1007                     self.interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], 'synchronizer')
1008                     requested_tx.append( (tx_hash, tx_height) )
1009             missing_tx = []
1010
1011             # detect if situation has changed
1012             if not self.interface.is_up_to_date('synchronizer'):
1013                 if self.wallet.is_up_to_date():
1014                     self.wallet.set_up_to_date(False)
1015                     self.was_updated = True
1016             else:
1017                 if not self.wallet.is_up_to_date():
1018                     self.wallet.set_up_to_date(True)
1019                     self.was_updated = True
1020
1021             if self.was_updated:
1022                 self.interface.trigger_callback('updated')
1023                 self.was_updated = False
1024
1025             # 2. get a response
1026             r = self.interface.get_response('synchronizer')
1027
1028             # poke sends None. (needed during stop)
1029             if not r: continue
1030
1031             # 3. handle response
1032             method = r['method']
1033             params = r['params']
1034             result = r.get('result')
1035             error = r.get('error')
1036             if error:
1037                 print "error", r
1038                 continue
1039
1040             if method == 'blockchain.address.subscribe':
1041                 addr = params[0]
1042                 if self.wallet.get_status(self.wallet.get_history(addr)) != result:
1043                     if requested_histories.get(addr) is None:
1044                         self.interface.send([('blockchain.address.get_history', [addr])], 'synchronizer')
1045                         requested_histories[addr] = result
1046
1047             elif method == 'blockchain.address.get_history':
1048                 addr = params[0]
1049                 print_error("receiving history", addr, result)
1050                 if result == ['*']:
1051                     assert requested_histories.pop(addr) == '*'
1052                     self.wallet.receive_history_callback(addr, result)
1053                 else:
1054                     hist = []
1055                     # check that txids are unique
1056                     txids = []
1057                     for item in result:
1058                         tx_hash = item['tx_hash']
1059                         if tx_hash not in txids:
1060                             txids.append(tx_hash)
1061                             hist.append( (tx_hash, item['height']) )
1062
1063                     if len(hist) != len(result):
1064                         raise BaseException("error: server sent history with non-unique txid", result)
1065
1066                     # check that the status corresponds to what was announced
1067                     rs = requested_histories.pop(addr)
1068                     if self.wallet.get_status(hist) != rs:
1069                         raise BaseException("error: status mismatch: %s"%addr)
1070                 
1071                     # store received history
1072                     self.wallet.receive_history_callback(addr, hist)
1073
1074                     # request transactions that we don't have 
1075                     for tx_hash, tx_height in hist:
1076                         if self.wallet.transactions.get(tx_hash) is None:
1077                             if (tx_hash, tx_height) not in requested_tx and (tx_hash, tx_height) not in missing_tx:
1078                                 missing_tx.append( (tx_hash, tx_height) )
1079
1080             elif method == 'blockchain.transaction.get':
1081                 tx_hash = params[0]
1082                 tx_height = params[1]
1083                 assert tx_hash == hash_encode(Hash(result.decode('hex')))
1084                 tx = Transaction(result)
1085                 self.wallet.receive_tx_callback(tx_hash, tx, tx_height)
1086                 self.was_updated = True
1087                 requested_tx.remove( (tx_hash, tx_height) )
1088                 print_error("received tx:", tx_hash, len(tx.raw))
1089
1090             elif method == 'blockchain.transaction.broadcast':
1091                 self.wallet.tx_result = result
1092                 self.wallet.tx_event.set()
1093
1094             else:
1095                 print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
1096
1097             if self.was_updated and not requested_tx:
1098                 self.interface.trigger_callback('updated')
1099                 self.was_updated = False
1100
1101