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