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