set default fee to 200 uBTC
[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         i = 0
433         for item in tx.outputs:
434             addr, value = item
435             key = tx_hash+ ':%d'%i
436             self.prevout_values[key] = value
437             i += 1
438
439         for item in tx.inputs:
440             if self.is_mine(item.get('address')):
441                 key = item['prevout_hash'] + ':%d'%item['prevout_n']
442                 self.spent_outputs.append(key)
443
444
445     def get_addr_balance(self, address):
446         assert self.is_mine(address)
447         h = self.history.get(address,[])
448         if h == ['*']: return 0,0
449         c = u = 0
450         received_coins = []   # list of coins received at address
451
452         for tx_hash, tx_height in h:
453             tx = self.transactions.get(tx_hash)
454             if not tx: continue
455             i = 0
456             for item in tx.outputs:
457                 addr, value = item
458                 if addr == address:
459                     key = tx_hash + ':%d'%i
460                     received_coins.append(key)
461                 i +=1
462
463         for tx_hash, tx_height in h:
464             tx = self.transactions.get(tx_hash)
465             if not tx: continue
466             v = 0
467
468             for item in tx.inputs:
469                 addr = item.get('address')
470                 if addr == address:
471                     key = item['prevout_hash']  + ':%d'%item['prevout_n']
472                     value = self.prevout_values.get( key )
473                     if key in received_coins: 
474                         v -= value
475
476             i = 0
477             for item in tx.outputs:
478                 addr, value = item
479                 key = tx_hash + ':%d'%i
480                 if addr == address:
481                     v += value
482                 i += 1
483
484             if tx_height:
485                 c += v
486             else:
487                 u += v
488         return c, u
489
490     def get_account_addresses(self, a):
491         ac = self.accounts[a]
492         return ac[0] + ac[1]
493
494     def get_imported_balance(self):
495         cc = uu = 0
496         for addr in self.imported_keys.keys():
497             c, u = self.get_addr_balance(addr)
498             cc += c
499             uu += u
500         return cc, uu
501
502     def get_account_balance(self, account):
503         conf = unconf = 0
504         for addr in self.get_account_addresses(account): 
505             c, u = self.get_addr_balance(addr)
506             conf += c
507             unconf += u
508         return conf, unconf
509
510     def get_balance(self):
511         cc = uu = 0
512         for a in self.accounts.keys():
513             c, u = self.get_account_balance(a)
514             cc += c
515             uu += u
516         c, u = self.get_imported_balance()
517         cc += c
518         uu += u
519         return cc, uu
520
521
522     def get_unspent_coins(self, domain=None):
523         coins = []
524         if domain is None: domain = self.addresses(True)
525         for addr in domain:
526             h = self.history.get(addr, [])
527             if h == ['*']: continue
528             for tx_hash, tx_height in h:
529                 tx = self.transactions.get(tx_hash)
530                 if tx is None: raise BaseException("Wallet not synchronized")
531                 for output in tx.d.get('outputs'):
532                     if output.get('address') != addr: continue
533                     key = tx_hash + ":%d" % output.get('index')
534                     if key in self.spent_outputs: continue
535                     output['tx_hash'] = tx_hash
536                     coins.append(output)
537         return coins
538
539
540
541     def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ):
542         """ todo: minimize tx size """
543         total = 0
544         fee = self.fee if fixed_fee is None else fixed_fee
545
546         coins = []
547         prioritized_coins = []
548         domain = [from_addr] if from_addr else self.addresses(True)
549         for i in self.frozen_addresses:
550             if i in domain: domain.remove(i)
551
552         for i in self.prioritized_addresses:
553             if i in domain: domain.remove(i)
554
555         coins = self.get_unspent_coins(domain)
556         prioritized_coins = self.get_unspent_coins(self.prioritized_addresses)
557
558         inputs = []
559         coins = prioritized_coins + coins
560
561         for item in coins: 
562             addr = item.get('address')
563             v = item.get('value')
564             total += v
565
566             inputs.append( item )
567             if fixed_fee is None:
568                 estimated_size =  len(inputs) * 180 + 80     # this assumes non-compressed keys
569                 fee = self.fee * round(estimated_size/1024.)
570                 if fee == 0: fee = self.fee
571             else:
572                 fee = fixed_fee
573             if total >= amount + fee: break
574         else:
575             inputs = []
576
577         return inputs, total, fee
578
579
580
581     def add_tx_change( self, outputs, amount, fee, total, change_addr=None ):
582         change_amount = total - ( amount + fee )
583         if change_amount != 0:
584             # normally, the update thread should ensure that the last change address is unused
585             if not change_addr:
586                 change_addresses = self.accounts[0][1]
587                 change_addr = change_addresses[-self.gap_limit_for_change]
588             # Insert the change output at a random position in the outputs
589             posn = random.randint(0, len(outputs))
590             outputs[posn:posn] = [( change_addr,  change_amount)]
591         return outputs
592
593
594     def get_history(self, address):
595         with self.lock:
596             return self.history.get(address)
597
598     def get_status(self, h):
599         if not h: return None
600         if h == ['*']: return '*'
601         status = ''
602         for tx_hash, height in h:
603             status += tx_hash + ':%d:' % height
604         return hashlib.sha256( status ).digest().encode('hex')
605
606
607
608     def receive_tx_callback(self, tx_hash, tx, tx_height):
609
610
611         if not self.check_new_tx(tx_hash, tx):
612             # may happen due to pruning
613             print_error("received transaction that is no longer referenced in history", tx_hash)
614             return
615
616         with self.transaction_lock:
617             self.transactions[tx_hash] = tx
618             if self.verifier and tx_height>0: 
619                 self.verifier.add(tx_hash, tx_height)
620             self.update_tx_outputs(tx_hash)
621
622         self.save()
623
624
625     def receive_history_callback(self, addr, hist):
626
627         if not self.check_new_history(addr, hist):
628             raise BaseException("error: received history for %s is not consistent with known transactions"%addr)
629             
630         with self.lock:
631             self.history[addr] = hist
632             self.save()
633
634         if hist != ['*']:
635             for tx_hash, tx_height in hist:
636                 if tx_height>0:
637                     # add it in case it was previously unconfirmed
638                     if self.verifier: self.verifier.add(tx_hash, tx_height)
639
640
641     def get_tx_history(self):
642         with self.transaction_lock:
643             history = self.transactions.items()
644             history.sort(key = lambda x: self.verifier.verified_tx.get(x[0]) if self.verifier.verified_tx.get(x[0]) else (1e12,0,0))
645             result = []
646     
647             balance = 0
648             for tx_hash, tx in history:
649                 is_mine, v, fee = self.get_tx_value(tx)
650                 if v is not None: balance += v
651             c, u = self.get_balance()
652
653             if balance != c+u:
654                 v_str = format_satoshis( c+u - balance, True, self.num_zeros)
655                 result.append( ('', 1000, 0, c+u-balance, None, c+u-balance, None ) )
656
657             balance = c + u - balance
658             for tx_hash, tx in history:
659                 conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None)
660                 is_mine, value, fee = self.get_tx_value(tx)
661                 if value is not None:
662                     balance += value
663
664                 result.append( (tx_hash, conf, is_mine, value, fee, balance, timestamp) )
665
666         return result
667
668
669     def get_label(self, tx_hash):
670         label = self.labels.get(tx_hash)
671         is_default = (label == '') or (label is None)
672         if is_default: label = self.get_default_label(tx_hash)
673         return label, is_default
674
675
676     def get_default_label(self, tx_hash):
677         tx = self.transactions.get(tx_hash)
678         default_label = ''
679         if tx:
680             is_mine, _, _ = self.get_tx_value(tx)
681             if is_mine:
682                 for o in tx.outputs:
683                     o_addr, _ = o
684                     if not self.is_mine(o_addr):
685                         try:
686                             default_label = self.labels[o_addr]
687                         except KeyError:
688                             default_label = o_addr
689                         break
690                 else:
691                     default_label = '(internal)'
692             else:
693                 for o in tx.outputs:
694                     o_addr, _ = o
695                     if self.is_mine(o_addr) and not self.is_change(o_addr):
696                         break
697                 else:
698                     for o in tx.outputs:
699                         o_addr, _ = o
700                         if self.is_mine(o_addr):
701                             break
702                     else:
703                         o_addr = None
704
705                 if o_addr:
706                     dest_label = self.labels.get(o_addr)
707                     try:
708                         default_label = self.labels[o_addr]
709                     except KeyError:
710                         default_label = o_addr
711
712         return default_label
713
714
715     def mktx(self, outputs, password, fee=None, change_addr=None, from_addr= None):
716
717         for address, x in outputs:
718             assert is_valid(address)
719
720         amount = sum( map(lambda x:x[1], outputs) )
721         inputs, total, fee = self.choose_tx_inputs( amount, fee, from_addr )
722         if not inputs:
723             raise ValueError("Not enough funds")
724
725         if not self.use_change and not change_addr:
726             change_addr = inputs[-1]['address']
727             print_error( "Sending change to", change_addr )
728         outputs = self.add_tx_change(outputs, amount, fee, total, change_addr)
729
730         tx = Transaction.from_io(inputs, outputs)
731
732         pk_addresses = []
733         for i in range(len(tx.inputs)):
734             txin = tx.inputs[i]
735             address = txin['address']
736             if address in self.imported_keys.keys(): 
737                 pk_addresses.append(address)
738                 continue
739             account, sequence = self.get_address_index(address)
740             txin['KeyID'] = (account, 'Electrum', sequence) # used by the server to find the key
741             pk_addr, redeemScript = self.sequences[account].get_input_info(sequence)
742             if redeemScript: txin['redeemScript'] = redeemScript
743             pk_addresses.append(pk_addr)
744
745         # get all private keys at once.
746         if self.seed:
747             private_keys = self.get_private_keys(pk_addresses, password)
748             tx.sign(private_keys)
749
750         for address, x in outputs:
751             if address not in self.addressbook and not self.is_mine(address):
752                 self.addressbook.append(address)
753
754         return tx
755
756
757
758     def sendtx(self, tx):
759         # synchronous
760         h = self.send_tx(tx)
761         self.tx_event.wait()
762         return self.receive_tx(h)
763
764     def send_tx(self, tx):
765         # asynchronous
766         self.tx_event.clear()
767         self.interface.send([('blockchain.transaction.broadcast', [str(tx)])], 'synchronizer')
768         return tx.hash()
769
770     def receive_tx(self,tx_hash):
771         out = self.tx_result 
772         if out != tx_hash:
773             return False, "error: " + out
774         return True, out
775
776
777
778     def update_password(self, seed, old_password, new_password):
779         if new_password == '': new_password = None
780         self.use_encryption = (new_password != None)
781         self.seed = pw_encode( seed, new_password)
782         self.config.set_key('seed', self.seed, True)
783         for k in self.imported_keys.keys():
784             a = self.imported_keys[k]
785             b = pw_decode(a, old_password)
786             c = pw_encode(b, new_password)
787             self.imported_keys[k] = c
788         self.save()
789
790
791
792     def freeze(self,addr):
793         if self.is_mine(addr) and addr not in self.frozen_addresses:
794             self.unprioritize(addr)
795             self.frozen_addresses.append(addr)
796             self.config.set_key('frozen_addresses', self.frozen_addresses, True)
797             return True
798         else:
799             return False
800
801     def unfreeze(self,addr):
802         if self.is_mine(addr) and addr in self.frozen_addresses:
803             self.frozen_addresses.remove(addr)
804             self.config.set_key('frozen_addresses', self.frozen_addresses, True)
805             return True
806         else:
807             return False
808
809     def prioritize(self,addr):
810         if self.is_mine(addr) and addr not in self.prioritized_addresses:
811             self.unfreeze(addr)
812             self.prioritized_addresses.append(addr)
813             self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
814             return True
815         else:
816             return False
817
818     def unprioritize(self,addr):
819         if self.is_mine(addr) and addr in self.prioritized_addresses:
820             self.prioritized_addresses.remove(addr)
821             self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
822             return True
823         else:
824             return False
825
826     def save(self):
827         tx = {}
828         for k,v in self.transactions.items():
829             tx[k] = str(v)
830             
831         s = {
832             'use_encryption': self.use_encryption,
833             'use_change': self.use_change,
834             'fee_per_kb': self.fee,
835             'accounts': self.accounts,
836             'addr_history': self.history, 
837             'labels': self.labels,
838             'contacts': self.addressbook,
839             'imported_keys': self.imported_keys,
840             'num_zeros': self.num_zeros,
841             'frozen_addresses': self.frozen_addresses,
842             'prioritized_addresses': self.prioritized_addresses,
843             'gap_limit': self.gap_limit,
844             'transactions': tx,
845         }
846         for k, v in s.items():
847             self.config.set_key(k,v)
848         self.config.save()
849
850     def set_verifier(self, verifier):
851         self.verifier = verifier
852
853         # review transactions that are in the history
854         for addr, hist in self.history.items():
855             if hist == ['*']: continue
856             for tx_hash, tx_height in hist:
857                 if tx_height>0:
858                     # add it in case it was previously unconfirmed
859                     self.verifier.add(tx_hash, tx_height)
860
861
862         # if we are on a pruning server, remove unverified transactions
863         vr = self.verifier.transactions.keys() + self.verifier.verified_tx.keys()
864         for tx_hash in self.transactions.keys():
865             if tx_hash not in vr:
866                 self.transactions.pop(tx_hash)
867
868
869
870     def check_new_history(self, addr, hist):
871         
872         # check that all tx in hist are relevant
873         if hist != ['*']:
874             for tx_hash, height in hist:
875                 tx = self.transactions.get(tx_hash)
876                 if not tx: continue
877                 if not tx.has_address(addr):
878                     return False
879
880         # check that we are not "orphaning" a transaction
881         old_hist = self.history.get(addr,[])
882         if old_hist == ['*']: return True
883
884         for tx_hash, height in old_hist:
885             if tx_hash in map(lambda x:x[0], hist): continue
886             found = False
887             for _addr, _hist in self.history.items():
888                 if _addr == addr: continue
889                 if _hist == ['*']: continue
890                 _tx_hist = map(lambda x:x[0], _hist)
891                 if tx_hash in _tx_hist:
892                     found = True
893                     break
894
895             if not found:
896                 tx = self.transactions.get(tx_hash)
897                 # tx might not be there
898                 if not tx: continue
899                 
900                 # already verified?
901                 if self.verifier.get_height(tx_hash):
902                     continue
903                 # unconfirmed tx
904                 print_error("new history is orphaning transaction:", tx_hash)
905                 # check that all outputs are not mine, request histories
906                 ext_requests = []
907                 for _addr, _v in tx.outputs:
908                     # assert not self.is_mine(_addr)
909                     ext_requests.append( ('blockchain.address.get_history', [_addr]) )
910
911                 ext_h = self.interface.synchronous_get(ext_requests)
912                 height = None
913                 for h in ext_h:
914                     if h == ['*']: continue
915                     print_error(h)
916                     for item in h:
917                         if item.get('tx_hash') == tx_hash:
918                             height = item.get('height')
919                 if height:
920                     print_error("found height for", tx_hash, height)
921                     self.verifier.add(tx_hash, height)
922                 else:
923                     print_error("removing orphaned tx from history", tx_hash)
924                     self.transactions.pop(tx_hash)
925
926         return True
927
928
929
930     def check_new_tx(self, tx_hash, tx):
931         # 1 check that tx is referenced in addr_history. 
932         addresses = []
933         for addr, hist in self.history.items():
934             if hist == ['*']:continue
935             for txh, height in hist:
936                 if txh == tx_hash: 
937                     addresses.append(addr)
938
939         if not addresses:
940             return False
941
942         # 2 check that referencing addresses are in the tx
943         for addr in addresses:
944             if not tx.has_address(addr):
945                 return False
946
947         return True
948
949
950
951
952 class WalletSynchronizer(threading.Thread):
953
954
955     def __init__(self, wallet, config):
956         threading.Thread.__init__(self)
957         self.daemon = True
958         self.wallet = wallet
959         self.interface = self.wallet.interface
960         self.interface.register_channel('synchronizer')
961         self.wallet.interface.register_callback('connected', lambda: self.wallet.set_up_to_date(False))
962         self.was_updated = True
963         self.running = False
964         self.lock = threading.Lock()
965
966     def stop(self):
967         with self.lock: self.running = False
968         self.interface.poke('synchronizer')
969
970     def is_running(self):
971         with self.lock: return self.running
972
973     
974     def subscribe_to_addresses(self, addresses):
975         messages = []
976         for addr in addresses:
977             messages.append(('blockchain.address.subscribe', [addr]))
978         self.interface.send( messages, 'synchronizer')
979
980
981     def run(self):
982         with self.lock: self.running = True
983
984         requested_tx = []
985         missing_tx = []
986         requested_histories = {}
987
988         # request any missing transactions
989         for history in self.wallet.history.values():
990             if history == ['*']: continue
991             for tx_hash, tx_height in history:
992                 if self.wallet.transactions.get(tx_hash) is None and (tx_hash, tx_height) not in missing_tx:
993                     missing_tx.append( (tx_hash, tx_height) )
994         print_error("missing tx", missing_tx)
995
996         # wait until we are connected, in case the user is not connected
997         while not self.interface.is_connected:
998             time.sleep(1)
999         
1000         # subscriptions
1001         self.subscribe_to_addresses(self.wallet.addresses(True))
1002
1003         while self.is_running():
1004             # 1. create new addresses
1005             new_addresses = self.wallet.synchronize()
1006
1007             # request missing addresses
1008             if new_addresses:
1009                 self.subscribe_to_addresses(new_addresses)
1010
1011             # request missing transactions
1012             for tx_hash, tx_height in missing_tx:
1013                 if (tx_hash, tx_height) not in requested_tx:
1014                     self.interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], 'synchronizer')
1015                     requested_tx.append( (tx_hash, tx_height) )
1016             missing_tx = []
1017
1018             # detect if situation has changed
1019             if not self.interface.is_up_to_date('synchronizer'):
1020                 if self.wallet.is_up_to_date():
1021                     self.wallet.set_up_to_date(False)
1022                     self.was_updated = True
1023             else:
1024                 if not self.wallet.is_up_to_date():
1025                     self.wallet.set_up_to_date(True)
1026                     self.was_updated = True
1027
1028             if self.was_updated:
1029                 self.interface.trigger_callback('updated')
1030                 self.was_updated = False
1031
1032             # 2. get a response
1033             r = self.interface.get_response('synchronizer')
1034
1035             # poke sends None. (needed during stop)
1036             if not r: continue
1037
1038             # 3. handle response
1039             method = r['method']
1040             params = r['params']
1041             result = r.get('result')
1042             error = r.get('error')
1043             if error:
1044                 print "error", r
1045                 continue
1046
1047             if method == 'blockchain.address.subscribe':
1048                 addr = params[0]
1049                 if self.wallet.get_status(self.wallet.get_history(addr)) != result:
1050                     if requested_histories.get(addr) is None:
1051                         self.interface.send([('blockchain.address.get_history', [addr])], 'synchronizer')
1052                         requested_histories[addr] = result
1053
1054             elif method == 'blockchain.address.get_history':
1055                 addr = params[0]
1056                 print_error("receiving history", addr, result)
1057                 if result == ['*']:
1058                     assert requested_histories.pop(addr) == '*'
1059                     self.wallet.receive_history_callback(addr, result)
1060                 else:
1061                     hist = []
1062                     # check that txids are unique
1063                     txids = []
1064                     for item in result:
1065                         tx_hash = item['tx_hash']
1066                         if tx_hash not in txids:
1067                             txids.append(tx_hash)
1068                             hist.append( (tx_hash, item['height']) )
1069
1070                     if len(hist) != len(result):
1071                         raise BaseException("error: server sent history with non-unique txid", result)
1072
1073                     # check that the status corresponds to what was announced
1074                     rs = requested_histories.pop(addr)
1075                     if self.wallet.get_status(hist) != rs:
1076                         raise BaseException("error: status mismatch: %s"%addr)
1077                 
1078                     # store received history
1079                     self.wallet.receive_history_callback(addr, hist)
1080
1081                     # request transactions that we don't have 
1082                     for tx_hash, tx_height in hist:
1083                         if self.wallet.transactions.get(tx_hash) is None:
1084                             if (tx_hash, tx_height) not in requested_tx and (tx_hash, tx_height) not in missing_tx:
1085                                 missing_tx.append( (tx_hash, tx_height) )
1086
1087             elif method == 'blockchain.transaction.get':
1088                 tx_hash = params[0]
1089                 tx_height = params[1]
1090                 assert tx_hash == hash_encode(Hash(result.decode('hex')))
1091                 tx = Transaction(result)
1092                 self.wallet.receive_tx_callback(tx_hash, tx, tx_height)
1093                 self.was_updated = True
1094                 requested_tx.remove( (tx_hash, tx_height) )
1095                 print_error("received tx:", tx)
1096
1097             elif method == 'blockchain.transaction.broadcast':
1098                 self.wallet.tx_result = result
1099                 self.wallet.tx_event.set()
1100
1101             else:
1102                 print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
1103
1104             if self.was_updated and not requested_tx:
1105                 self.interface.trigger_callback('updated')
1106                 self.was_updated = False
1107
1108