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