fix: sign_message
[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, format_satoshis
34 from bitcoin import *
35 from account import *
36 from transaction import Transaction
37
38 # AES encryption
39 EncodeAES = lambda secret, s: base64.b64encode(aes.encryptData(secret,s))
40 DecodeAES = lambda secret, e: aes.decryptData(secret, base64.b64decode(e))
41
42 def pw_encode(s, password):
43     if password:
44         secret = Hash(password)
45         return EncodeAES(secret, s)
46     else:
47         return s
48
49 def pw_decode(s, password):
50     if password is not None:
51         secret = Hash(password)
52         try:
53             d = DecodeAES(secret, s)
54         except:
55             raise BaseException('Invalid password')
56         return d
57     else:
58         return s
59
60
61
62
63
64 from version import ELECTRUM_VERSION, SEED_VERSION
65
66
67 class WalletStorage:
68
69     def __init__(self, config):
70         self.data = {}
71         self.file_exists = False
72         self.init_path(config)
73         print_error( "wallet path", self.path )
74         if self.path:
75             self.read(self.path)
76
77
78     def init_path(self, config):
79         """Set the path of the wallet."""
80
81         path = config.get('wallet_path')
82         if not path:
83             path = config.get('default_wallet_path')
84         if path is not None:
85             self.path = path
86             return
87
88         self.path = os.path.join(config.path, "electrum.dat")
89
90
91     def read(self, path):
92         """Read the contents of the wallet file."""
93         try:
94             with open(self.path, "r") as f:
95                 data = f.read()
96         except IOError:
97             return
98         try:
99             d = ast.literal_eval( data )  #parse raw data from reading wallet file
100         except:
101             raise IOError("Cannot read wallet file.")
102
103         self.data = d
104         self.file_exists = True
105
106
107     def get(self, key, default=None):
108         return self.data.get(key, default)
109
110     def put(self, key, value, save = True):
111
112         if self.data.get(key) is not None:
113             self.data[key] = value
114         else:
115             # add key to wallet config
116             self.data[key] = value
117
118         if save: 
119             self.write()
120
121
122     def write(self):
123         s = repr(self.data)
124         f = open(self.path,"w")
125         f.write( s )
126         f.close()
127         if self.get('gui') != 'android':
128             import stat
129             os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE)
130
131
132 class Wallet:
133
134     def __init__(self, storage):
135
136         self.storage = storage
137         self.electrum_version = ELECTRUM_VERSION
138         self.gap_limit_for_change = 3 # constant
139
140         # saved fields
141         self.seed_version          = storage.get('seed_version', SEED_VERSION)
142
143         self.gap_limit             = storage.get('gap_limit', 5)
144         self.use_change            = storage.get('use_change',True)
145         self.use_encryption        = storage.get('use_encryption', False)
146         self.seed                  = storage.get('seed', '')               # encrypted
147         self.labels                = storage.get('labels', {})
148         self.frozen_addresses      = storage.get('frozen_addresses',[])
149         self.prioritized_addresses = storage.get('prioritized_addresses',[])
150         self.addressbook           = storage.get('contacts', [])
151
152         self.imported_keys         = storage.get('imported_keys',{})
153         self.history               = storage.get('addr_history',{})        # address -> list(txid, height)
154
155         self.fee                   = int(storage.get('fee_per_kb',20000))
156
157         self.master_public_keys = storage.get('master_public_keys',{})
158         self.master_private_keys = storage.get('master_private_keys', {})
159
160         self.first_addresses = storage.get('first_addresses',{})
161
162         if self.seed_version < 4:
163             raise ValueError("This wallet seed is deprecated.")
164
165         self.load_accounts()
166
167         self.transactions = {}
168         tx_list = self.storage.get('transactions',{})
169         for k,v in tx_list.items():
170             try:
171                 tx = Transaction(v)
172             except:
173                 print_msg("Warning: Cannot deserialize transactions. skipping")
174                 continue
175
176             self.add_extra_addresses(tx)
177             self.transactions[k] = tx
178
179         for h,tx in self.transactions.items():
180             if not self.check_new_tx(h, tx):
181                 print_error("removing unreferenced tx", h)
182                 self.transactions.pop(h)
183
184
185         # not saved
186         self.prevout_values = {}     # my own transaction outputs
187         self.spent_outputs = []
188
189         # spv
190         self.verifier = None
191
192         # there is a difference between wallet.up_to_date and interface.is_up_to_date()
193         # interface.is_up_to_date() returns true when all requests have been answered and processed
194         # wallet.up_to_date is true when the wallet is synchronized (stronger requirement)
195         
196         self.up_to_date = False
197         self.lock = threading.Lock()
198         self.transaction_lock = threading.Lock()
199         self.tx_event = threading.Event()
200
201         for tx_hash, tx in self.transactions.items():
202             self.update_tx_outputs(tx_hash)
203
204
205     def add_extra_addresses(self, tx):
206         h = tx.hash()
207         # find the address corresponding to pay-to-pubkey inputs
208         tx.add_extra_addresses(self.transactions)
209         for o in tx.d.get('outputs'):
210             if o.get('is_pubkey'):
211                 for tx2 in self.transactions.values():
212                     tx2.add_extra_addresses({h:tx})
213
214             
215
216
217     def set_up_to_date(self,b):
218         with self.lock: self.up_to_date = b
219
220     def is_up_to_date(self):
221         with self.lock: return self.up_to_date
222
223     def update(self):
224         self.up_to_date = False
225         self.interface.poke('synchronizer')
226         while not self.is_up_to_date(): time.sleep(0.1)
227
228     def import_key(self, sec, password):
229         # check password
230         seed = self.decode_seed(password)
231         try:
232             address = address_from_private_key(sec)
233         except:
234             raise BaseException('Invalid private key')
235
236         if self.is_mine(address):
237             raise BaseException('Address already in wallet')
238         
239         # store the originally requested keypair into the imported keys table
240         self.imported_keys[address] = pw_encode(sec, password )
241         self.storage.put('imported_keys', self.imported_keys, True)
242         return address
243         
244     def delete_imported_key(self, addr):
245         if addr in self.imported_keys:
246             self.imported_keys.pop(addr)
247             self.storage.put('imported_keys', self.imported_keys, True)
248
249
250     def init_seed(self, seed):
251         if self.seed: raise BaseException("a seed exists")
252         if not seed: 
253             seed = random_seed(128)
254         self.seed = seed
255
256
257     def save_seed(self):
258         self.storage.put('seed', self.seed, True)
259         self.storage.put('seed_version', self.seed_version, True)
260
261     def create_watching_only_wallet(self, c0, K0):
262         cK0 = ""
263         self.master_public_keys = {
264             "m/0'/": (c0, K0, cK0),
265             }
266         self.storage.put('master_public_keys', self.master_public_keys, True)
267         self.create_account('1','Main account')
268
269     def create_accounts(self):
270
271         master_k, master_c, master_K, master_cK = bip32_init(self.seed)
272         
273         # normal accounts
274         k0, c0, K0, cK0 = bip32_private_derivation(master_k, master_c, "m/", "m/0'/")
275         # p2sh 2of2
276         k1, c1, K1, cK1 = bip32_private_derivation(master_k, master_c, "m/", "m/1'/")
277         k2, c2, K2, cK2 = bip32_private_derivation(master_k, master_c, "m/", "m/2'/")
278         # p2sh 2of3
279         k3, c3, K3, cK3 = bip32_private_derivation(master_k, master_c, "m/", "m/3'/")
280         k4, c4, K4, cK4 = bip32_private_derivation(master_k, master_c, "m/", "m/4'/")
281         k5, c5, K5, cK5 = bip32_private_derivation(master_k, master_c, "m/", "m/5'/")
282
283         self.master_public_keys = {
284             "m/0'/": (c0, K0, cK0),
285             "m/1'/": (c1, K1, cK1),
286             "m/2'/": (c2, K2, cK2),
287             "m/3'/": (c3, K3, cK3),
288             "m/4'/": (c4, K4, cK4),
289             "m/5'/": (c5, K5, cK5)
290             }
291         
292         self.master_private_keys = {
293             "m/0'/": k0,
294             "m/1'/": k1,
295             "m/2'/": k2,
296             "m/3'/": k3,
297             "m/4'/": k4,
298             "m/5'/": k5
299             }
300         
301         self.storage.put('master_public_keys', self.master_public_keys, True)
302         self.storage.put('master_private_keys', self.master_private_keys, True)
303
304         # create default account
305         self.create_account('1','Main account')
306
307
308     def find_root_by_master_key(self, c, K):
309         for key, v in self.master_public_keys.items():
310             if key == "m/":continue
311             cc, KK, _ = v
312             if (c == cc) and (K == KK):
313                 return key
314
315     def deseed_root(self, seed, password):
316         # for safety, we ask the user to enter their seed
317         assert seed == self.decode_seed(password)
318         self.seed = ''
319         self.storage.put('seed', '', True)
320
321
322     def deseed_branch(self, k):
323         # check that parent has no seed
324         assert self.seed == ''
325         self.master_private_keys.pop(k)
326         self.storage.put('master_private_keys', self.master_private_keys, True)
327
328
329     def account_id(self, account_type, i):
330         if account_type == '1':
331             return "m/0'/%d"%i
332         elif account_type == '2of2':
333             return "m/1'/%d & m/2'/%d"%(i,i)
334         elif account_type == '2of3':
335             return "m/3'/%d & m/4'/%d & m/5'/%d"%(i,i,i)
336         else:
337             raise BaseException('unknown account type')
338
339
340     def num_accounts(self, account_type):
341         keys = self.accounts.keys()
342         i = 0
343         while True:
344             account_id = self.account_id(account_type, i)
345             if account_id not in keys: break
346             i += 1
347         return i
348
349
350     def new_account_address(self, account_type = '1'):
351         i = self.num_accounts(account_type)
352         k = self.account_id(account_type,i)
353
354         addr = self.first_addresses.get(k)
355         if not addr: 
356             account_id, account = self.next_account(account_type)
357             addr = account.first_address()
358             self.first_addresses[k] = addr
359             self.storage.put('first_addresses',self.first_addresses)
360
361         return addr
362
363
364     def next_account(self, account_type = '1'):
365
366         i = self.num_accounts(account_type)
367         account_id = self.account_id(account_type,i)
368
369         if account_type is '1':
370             master_c0, master_K0, _ = self.master_public_keys["m/0'/"]
371             c0, K0, cK0 = bip32_public_derivation(master_c0.decode('hex'), master_K0.decode('hex'), "m/0'/", "m/0'/%d"%i)
372             account = BIP32_Account({ 'c':c0, 'K':K0, 'cK':cK0 })
373
374         elif account_type == '2of2':
375             master_c1, master_K1, _ = self.master_public_keys["m/1'/"]
376             c1, K1, cK1 = bip32_public_derivation(master_c1.decode('hex'), master_K1.decode('hex'), "m/1'/", "m/1'/%d"%i)
377             master_c2, master_K2, _ = self.master_public_keys["m/2'/"]
378             c2, K2, cK2 = bip32_public_derivation(master_c2.decode('hex'), master_K2.decode('hex'), "m/2'/", "m/2'/%d"%i)
379             account = BIP32_Account_2of2({ 'c':c1, 'K':K1, 'cK':cK1, 'c2':c2, 'K2':K2, 'cK2':cK2 })
380
381         elif account_type == '2of3':
382             master_c3, master_K3, _ = self.master_public_keys["m/3'/"]
383             c3, K3, cK3 = bip32_public_derivation(master_c3.decode('hex'), master_K3.decode('hex'), "m/3'/", "m/3'/%d"%i)
384             master_c4, master_K4, _ = self.master_public_keys["m/4'/"]
385             c4, K4, cK4 = bip32_public_derivation(master_c4.decode('hex'), master_K4.decode('hex'), "m/4'/", "m/4'/%d"%i)
386             master_c5, master_K5, _ = self.master_public_keys["m/5'/"]
387             c5, K5, cK5 = bip32_public_derivation(master_c5.decode('hex'), master_K5.decode('hex'), "m/5'/", "m/5'/%d"%i)
388             account = BIP32_Account_2of3({ 'c':c3, 'K':K3, 'cK':cK3, 'c2':c4, 'K2':K4, 'cK2':cK4, 'c3':c5, 'K3':K5, 'cK3':cK5 })
389
390         return account_id, account
391
392
393     def set_label(self, key, value):
394         self.labels[key] = value
395         self.storage.put('labels', self.labels, True)
396
397
398     def create_account(self, account_type = '1', name = None):
399         account_id, account = self.next_account(account_type)
400         self.accounts[account_id] = account
401         self.save_accounts()
402         if name:
403             self.set_label(account_id, name)
404
405
406     def create_old_account(self):
407         mpk = OldAccount.mpk_from_seed(self.seed)
408         self.storage.put('master_public_key', mpk, True)
409         self.accounts[0] = OldAccount({'mpk':mpk, 0:[], 1:[]})
410         self.save_accounts()
411
412
413     def save_accounts(self):
414         d = {}
415         for k, v in self.accounts.items():
416             d[k] = v.dump()
417         self.storage.put('accounts', d, True)
418
419     
420
421     def load_accounts(self):
422         d = self.storage.get('accounts', {})
423         self.accounts = {}
424         for k, v in d.items():
425             if k == 0:
426                 v['mpk'] = self.storage.get('master_public_key')
427                 self.accounts[k] = OldAccount(v)
428             elif '&' in k:
429                 self.accounts[k] = BIP32_Account_2of2(v)
430             else:
431                 self.accounts[k] = BIP32_Account(v)
432
433
434     def addresses(self, include_change = True):
435         o = self.get_account_addresses(-1, include_change)
436         for a in self.accounts.keys():
437             o += self.get_account_addresses(a, include_change)
438
439         for addr in self.first_addresses.values():
440             if addr not in o:
441                 o += addr
442         return o
443
444
445     def is_mine(self, address):
446         return address in self.addresses(True)
447
448
449     def is_change(self, address):
450         if not self.is_mine(address): return False
451         if address in self.imported_keys.keys(): return False
452         acct, s = self.get_address_index(address)
453         return s[0] == 1
454
455     def get_master_public_key(self):
456         if self.seed_version == 4:
457             return self.storage.get("master_public_key")
458         else:
459             c, K, cK = self.storage.get("master_public_keys")["m/0'/"]
460             return repr((c, K))
461
462     def get_master_private_key(self, account, password):
463         master_k = pw_decode( self.master_private_keys[account], password)
464         master_c, master_K, master_Kc = self.master_public_keys[account]
465         try:
466             K, Kc = get_pubkeys_from_secret(master_k.decode('hex'))
467             assert K.encode('hex') == master_K
468         except:
469             raise BaseException("Invalid password")
470         return master_k
471
472
473     def get_address_index(self, address):
474         if address in self.imported_keys.keys():
475             return -1, None
476
477         for account in self.accounts.keys():
478             for for_change in [0,1]:
479                 addresses = self.accounts[account].get_addresses(for_change)
480                 for addr in addresses:
481                     if address == addr:
482                         return account, (for_change, addresses.index(addr))
483
484         raise BaseException("not found")
485
486
487     def rebase_sequence(self, account, sequence):
488         c, i = sequence
489         dd = []
490         for a in account.split('&'):
491             s = a.strip()
492             m = re.match("(m/\d+'/)(\d+)", s)
493             root = m.group(1)
494             num = int(m.group(2))
495             dd.append( (root, [num,c,i] ) )
496         return dd
497         
498
499     def get_keyID(self, account, sequence):
500         if account == 0:
501             return 'old'
502
503         rs = self.rebase_sequence(account, sequence)
504         dd = []
505         for root, public_sequence in rs:
506             c, K, _ = self.master_public_keys[root]
507             s = '/' + '/'.join( map(lambda x:str(x), public_sequence) )
508             dd.append( 'bip32(%s,%s,%s)'%(c,K, s) )
509         return '&'.join(dd)
510
511
512     def get_public_key(self, address):
513         account, sequence = self.get_address_index(address)
514         return self.accounts[account].get_pubkey( *sequence )
515
516
517     def decode_seed(self, password):
518         seed = pw_decode(self.seed, password)
519         #todo:  #self.sequences[0].check_seed(seed)
520         return seed
521         
522
523     def get_private_key(self, address, password):
524         out = []
525         if address in self.imported_keys.keys():
526             out.append( pw_decode( self.imported_keys[address], password ) )
527         else:
528             account, sequence = self.get_address_index(address)
529             if account == 0:
530                 seed = self.decode_seed(password)
531                 pk = self.accounts[account].get_private_key(seed, sequence)
532                 out.append(pk)
533                 return out
534
535             # assert address == self.accounts[account].get_address(*sequence)
536             rs = self.rebase_sequence( account, sequence)
537             for root, public_sequence in rs:
538
539                 if root not in self.master_private_keys.keys(): continue
540                 master_k = self.get_master_private_key(root, password)
541                 master_c, _, _ = self.master_public_keys[root]
542                 pk = bip32_private_key( public_sequence, master_k.decode('hex'), master_c.decode('hex'))
543                 out.append(pk)
544                     
545         return out
546
547
548
549
550     def signrawtransaction(self, tx, input_info, private_keys, password):
551
552         unspent_coins = self.get_unspent_coins()
553         seed = self.decode_seed(password)
554
555         # build a list of public/private keys
556         keypairs = {}
557         for sec in private_keys:
558             pubkey = public_key_from_private_key(sec)
559             keypairs[ pubkey ] = sec
560
561
562         for txin in tx.inputs:
563             # convert to own format
564             txin['tx_hash'] = txin['prevout_hash']
565             txin['index'] = txin['prevout_n']
566
567             for item in input_info:
568                 if item.get('txid') == txin['tx_hash'] and item.get('vout') == txin['index']:
569                     txin['raw_output_script'] = item['scriptPubKey']
570                     txin['redeemScript'] = item.get('redeemScript')
571                     txin['KeyID'] = item.get('KeyID')
572                     break
573             else:
574                 for item in unspent_coins:
575                     if txin['tx_hash'] == item['tx_hash'] and txin['index'] == item['index']:
576                         print_error( "tx input is in unspent coins" )
577                         txin['raw_output_script'] = item['raw_output_script']
578                         account, sequence = self.get_address_index(item['address'])
579                         if account != -1:
580                             txin['redeemScript'] = self.accounts[account].redeem_script(sequence)
581                         break
582                 else:
583                     raise BaseException("Unknown transaction input. Please provide the 'input_info' parameter, or synchronize this wallet")
584
585             # if available, derive private_keys from KeyID
586             keyid = txin.get('KeyID')
587             if keyid:
588                 roots = []
589                 for s in keyid.split('&'):
590                     m = re.match("bip32\(([0-9a-f]+),([0-9a-f]+),(/\d+/\d+/\d+)", s)
591                     if not m: continue
592                     c = m.group(1)
593                     K = m.group(2)
594                     sequence = m.group(3)
595                     root = self.find_root_by_master_key(c,K)
596                     if not root: continue
597                     sequence = map(lambda x:int(x), sequence.strip('/').split('/'))
598                     root = root + '%d'%sequence[0]
599                     sequence = sequence[1:]
600                     roots.append((root,sequence)) 
601
602                 account_id = " & ".join( map(lambda x:x[0], roots) )
603                 account = self.accounts.get(account_id)
604                 if not account: continue
605                 addr = account.get_address(*sequence)
606                 txin['address'] = addr
607                 pk = self.get_private_key(addr, password)
608                 for sec in pk:
609                     pubkey = public_key_from_private_key(sec)
610                     keypairs[pubkey] = sec
611
612             redeem_script = txin.get("redeemScript")
613             print_error( "p2sh:", "yes" if redeem_script else "no")
614             if redeem_script:
615                 addr = hash_160_to_bc_address(hash_160(redeem_script.decode('hex')), 5)
616             else:
617                 addr = transaction.get_address_from_output_script(txin["raw_output_script"].decode('hex'))
618             txin['address'] = addr
619
620             # add private keys that are in the wallet
621             pk = self.get_private_key(addr, password)
622             for sec in pk:
623                 pubkey = public_key_from_private_key(sec)
624                 keypairs[pubkey] = sec
625                 if not redeem_script:
626                     txin['redeemPubkey'] = pubkey
627
628             print txin
629
630         tx.sign( keypairs )
631
632     def sign_message(self, address, message, password):
633         keys = self.get_private_key(address, password)
634         assert len(keys) == 1
635         sec = keys[0]
636         key = regenerate_key(sec)
637         compressed = is_compressed(sec)
638         return key.sign_message(message, compressed, address)
639
640     def verify_message(self, address, signature, message):
641         try:
642             EC_KEY.verify_message(address, signature, message)
643             return True
644         except BaseException as e:
645             print_error("Verification error: {0}".format(e))
646             return False
647
648
649     def change_gap_limit(self, value):
650         if value >= self.gap_limit:
651             self.gap_limit = value
652             self.storage.put('gap_limit', self.gap_limit, True)
653             self.interface.poke('synchronizer')
654             return True
655
656         elif value >= self.min_acceptable_gap():
657             for key, account in self.accounts.items():
658                 addresses = account[0]
659                 k = self.num_unused_trailing_addresses(addresses)
660                 n = len(addresses) - k + value
661                 addresses = addresses[0:n]
662                 self.accounts[key][0] = addresses
663
664             self.gap_limit = value
665             self.storage.put('gap_limit', self.gap_limit, True)
666             self.save_accounts()
667             return True
668         else:
669             return False
670
671     def num_unused_trailing_addresses(self, addresses):
672         k = 0
673         for a in addresses[::-1]:
674             if self.history.get(a):break
675             k = k + 1
676         return k
677
678     def min_acceptable_gap(self):
679         # fixme: this assumes wallet is synchronized
680         n = 0
681         nmax = 0
682
683         for account in self.accounts.values():
684             addresses = account.get_addresses(0)
685             k = self.num_unused_trailing_addresses(addresses)
686             for a in addresses[0:-k]:
687                 if self.history.get(a):
688                     n = 0
689                 else:
690                     n += 1
691                     if n > nmax: nmax = n
692         return nmax + 1
693
694
695     def address_is_old(self, address):
696         age = -1
697         h = self.history.get(address, [])
698         if h == ['*']:
699             return True
700         for tx_hash, tx_height in h:
701             if tx_height == 0:
702                 tx_age = 0
703             else: 
704                 tx_age = self.verifier.blockchain.height - tx_height + 1
705             if tx_age > age:
706                 age = tx_age
707         return age > 2
708
709
710     def synchronize_sequence(self, account, for_change):
711         limit = self.gap_limit_for_change if for_change else self.gap_limit
712         new_addresses = []
713         while True:
714             addresses = account.get_addresses(for_change)
715             if len(addresses) < limit:
716                 address = account.create_new_address(for_change)
717                 self.history[address] = []
718                 new_addresses.append( address )
719                 continue
720
721             if map( lambda a: self.address_is_old(a), addresses[-limit:] ) == limit*[False]:
722                 break
723             else:
724                 address = account.create_new_address(for_change)
725                 self.history[address] = []
726                 new_addresses.append( address )
727
728         return new_addresses
729         
730
731
732     def create_pending_accounts(self):
733         for account_type in ['1','2of2','2of3']:
734             a = self.new_account_address(account_type)
735             if self.address_is_old(a):
736                 print_error( "creating account", a )
737                 self.create_account(account_type)
738
739
740     def synchronize_account(self, account):
741         new = []
742         new += self.synchronize_sequence(account, 0)
743         new += self.synchronize_sequence(account, 1)
744         return new
745
746
747     def synchronize(self):
748         if self.master_public_keys:
749             self.create_pending_accounts()
750         new = []
751         for account in self.accounts.values():
752             new += self.synchronize_account(account)
753         if new:
754             self.save_accounts()
755             self.storage.put('addr_history', self.history, True)
756         return new
757
758
759     def is_found(self):
760         return self.history.values() != [[]] * len(self.history) 
761
762
763     def add_contact(self, address, label=None):
764         self.addressbook.append(address)
765         self.storage.put('contacts', self.addressbook, True)
766         if label:  
767             self.set_label(address, label)
768
769
770     def delete_contact(self, addr):
771         if addr in self.addressbook:
772             self.addressbook.remove(addr)
773             self.storage.put('addressbook', self.addressbook, True)
774
775
776     def fill_addressbook(self):
777         for tx_hash, tx in self.transactions.items():
778             is_relevant, is_send, _, _ = self.get_tx_value(tx)
779             if is_send:
780                 for addr, v in tx.outputs:
781                     if not self.is_mine(addr) and addr not in self.addressbook:
782                         self.addressbook.append(addr)
783         # redo labels
784         # self.update_tx_labels()
785
786     def get_num_tx(self, address):
787         n = 0 
788         for tx in self.transactions.values():
789             if address in map(lambda x:x[0], tx.outputs): n += 1
790         return n
791
792
793     def get_address_flags(self, addr):
794         flags = "C" if self.is_change(addr) else "I" if addr in self.imported_keys.keys() else "-" 
795         flags += "F" if addr in self.frozen_addresses else "P" if addr in self.prioritized_addresses else "-"
796         return flags
797         
798
799     def get_tx_value(self, tx, account=None):
800         domain = self.get_account_addresses(account)
801         return tx.get_value(domain, self.prevout_values)
802
803     
804     def update_tx_outputs(self, tx_hash):
805         tx = self.transactions.get(tx_hash)
806
807         for i, (addr, value) in enumerate(tx.outputs):
808             key = tx_hash+ ':%d'%i
809             self.prevout_values[key] = value
810
811         for item in tx.inputs:
812             if self.is_mine(item.get('address')):
813                 key = item['prevout_hash'] + ':%d'%item['prevout_n']
814                 self.spent_outputs.append(key)
815
816
817     def get_addr_balance(self, address):
818         assert self.is_mine(address)
819         h = self.history.get(address,[])
820         if h == ['*']: return 0,0
821         c = u = 0
822         received_coins = []   # list of coins received at address
823
824         for tx_hash, tx_height in h:
825             tx = self.transactions.get(tx_hash)
826             if not tx: continue
827
828             for i, (addr, value) in enumerate(tx.outputs):
829                 if addr == address:
830                     key = tx_hash + ':%d'%i
831                     received_coins.append(key)
832
833         for tx_hash, tx_height in h:
834             tx = self.transactions.get(tx_hash)
835             if not tx: continue
836             v = 0
837
838             for item in tx.inputs:
839                 addr = item.get('address')
840                 if addr == address:
841                     key = item['prevout_hash']  + ':%d'%item['prevout_n']
842                     value = self.prevout_values.get( key )
843                     if key in received_coins: 
844                         v -= value
845
846             for i, (addr, value) in enumerate(tx.outputs):
847                 key = tx_hash + ':%d'%i
848                 if addr == address:
849                     v += value
850
851             if tx_height:
852                 c += v
853             else:
854                 u += v
855         return c, u
856
857
858     def get_account_name(self, k):
859         if k == 0:
860             if self.seed_version == 4: 
861                 name = 'Main account'
862             else:
863                 name = 'Old account'
864         else:
865             name = self.labels.get(k, 'Unnamed account')
866         return name
867
868     def get_account_names(self):
869         accounts = {}
870         for k, account in self.accounts.items():
871             accounts[k] = self.get_account_name(k)
872         if self.imported_keys:
873             accounts[-1] = 'Imported keys'
874         return accounts
875
876     def get_account_addresses(self, a, include_change=True):
877         if a is None:
878             o = self.addresses(True)
879         elif a == -1:
880             o = self.imported_keys.keys()
881         else:
882             ac = self.accounts[a]
883             o = ac.get_addresses(0)
884             if include_change: o += ac.get_addresses(1)
885         return o
886
887     def get_imported_balance(self):
888         cc = uu = 0
889         for addr in self.imported_keys.keys():
890             c, u = self.get_addr_balance(addr)
891             cc += c
892             uu += u
893         return cc, uu
894
895     def get_account_balance(self, account):
896         if account is None:
897             return self.get_balance()
898         elif account == -1:
899             return self.get_imported_balance()
900         
901         conf = unconf = 0
902         for addr in self.get_account_addresses(account): 
903             c, u = self.get_addr_balance(addr)
904             conf += c
905             unconf += u
906         return conf, unconf
907
908     def get_frozen_balance(self):
909         conf = unconf = 0
910         for addr in self.frozen_addresses:
911             c, u = self.get_addr_balance(addr)
912             conf += c
913             unconf += u
914         return conf, unconf
915
916         
917     def get_balance(self):
918         cc = uu = 0
919         for a in self.accounts.keys():
920             c, u = self.get_account_balance(a)
921             cc += c
922             uu += u
923         c, u = self.get_imported_balance()
924         cc += c
925         uu += u
926         return cc, uu
927
928
929     def get_unspent_coins(self, domain=None):
930         coins = []
931         if domain is None: domain = self.addresses(True)
932         for addr in domain:
933             h = self.history.get(addr, [])
934             if h == ['*']: continue
935             for tx_hash, tx_height in h:
936                 tx = self.transactions.get(tx_hash)
937                 if tx is None: raise BaseException("Wallet not synchronized")
938                 for output in tx.d.get('outputs'):
939                     if output.get('address') != addr: continue
940                     key = tx_hash + ":%d" % output.get('index')
941                     if key in self.spent_outputs: continue
942                     output['tx_hash'] = tx_hash
943                     coins.append(output)
944         return coins
945
946
947
948     def choose_tx_inputs( self, amount, fixed_fee, account = None ):
949         """ todo: minimize tx size """
950         total = 0
951         fee = self.fee if fixed_fee is None else fixed_fee
952         domain = self.get_account_addresses(account)
953         coins = []
954         prioritized_coins = []
955         for i in self.frozen_addresses:
956             if i in domain: domain.remove(i)
957
958         for i in self.prioritized_addresses:
959             if i in domain: domain.remove(i)
960
961         coins = self.get_unspent_coins(domain)
962         prioritized_coins = self.get_unspent_coins(self.prioritized_addresses)
963
964         inputs = []
965         coins = prioritized_coins + coins
966
967         for item in coins: 
968             addr = item.get('address')
969             v = item.get('value')
970             total += v
971             inputs.append( item )
972             fee = self.estimated_fee(inputs) if fixed_fee is None else fixed_fee
973             if total >= amount + fee: break
974         else:
975             inputs = []
976
977         return inputs, total, fee
978
979
980     def set_fee(self, fee):
981         if self.fee != fee:
982             self.fee = fee
983             self.storage.put('fee_per_kb', self.fee, True)
984         
985     def estimated_fee(self, inputs):
986         estimated_size =  len(inputs) * 180 + 80     # this assumes non-compressed keys
987         fee = self.fee * int(round(estimated_size/1024.))
988         if fee == 0: fee = self.fee
989         return fee
990
991
992     def add_tx_change( self, inputs, outputs, amount, fee, total, change_addr=None, account=0 ):
993         "add change to a transaction"
994         change_amount = total - ( amount + fee )
995         if change_amount != 0:
996             if not change_addr:
997                 if account is None: 
998                     # send change to one of the accounts involved in the tx
999                     address = inputs[0].get('address')
1000                     account, _ = self.get_address_index(address)
1001
1002                 if not self.use_change or account == -1:
1003                     change_addr = inputs[-1]['address']
1004                 else:
1005                     change_addr = self.accounts[account].get_addresses(1)[-self.gap_limit_for_change]
1006
1007             # Insert the change output at a random position in the outputs
1008             posn = random.randint(0, len(outputs))
1009             outputs[posn:posn] = [( change_addr,  change_amount)]
1010         return outputs
1011
1012
1013     def get_history(self, address):
1014         with self.lock:
1015             return self.history.get(address)
1016
1017
1018     def get_status(self, h):
1019         if not h: return None
1020         if h == ['*']: return '*'
1021         status = ''
1022         for tx_hash, height in h:
1023             status += tx_hash + ':%d:' % height
1024         return hashlib.sha256( status ).digest().encode('hex')
1025
1026
1027     def receive_tx_callback(self, tx_hash, tx, tx_height):
1028
1029         with self.transaction_lock:
1030             self.add_extra_addresses(tx)
1031             if not self.check_new_tx(tx_hash, tx):
1032                 # may happen due to pruning
1033                 print_error("received transaction that is no longer referenced in history", tx_hash)
1034                 return
1035             self.transactions[tx_hash] = tx
1036             self.interface.pending_transactions_for_notifications.append(tx)
1037             self.save_transactions()
1038             if self.verifier and tx_height>0: 
1039                 self.verifier.add(tx_hash, tx_height)
1040             self.update_tx_outputs(tx_hash)
1041
1042
1043     def save_transactions(self):
1044         tx = {}
1045         for k,v in self.transactions.items():
1046             tx[k] = str(v)
1047         self.storage.put('transactions', tx, True)
1048
1049     def receive_history_callback(self, addr, hist):
1050
1051         if not self.check_new_history(addr, hist):
1052             raise BaseException("error: received history for %s is not consistent with known transactions"%addr)
1053             
1054         with self.lock:
1055             self.history[addr] = hist
1056             self.storage.put('addr_history', self.history, True)
1057
1058         if hist != ['*']:
1059             for tx_hash, tx_height in hist:
1060                 if tx_height>0:
1061                     # add it in case it was previously unconfirmed
1062                     if self.verifier: self.verifier.add(tx_hash, tx_height)
1063
1064
1065     def get_tx_history(self, account=None):
1066         with self.transaction_lock:
1067             history = self.transactions.items()
1068             history.sort(key = lambda x: self.verifier.get_txpos(x[0]))
1069             result = []
1070     
1071             balance = 0
1072             for tx_hash, tx in history:
1073                 is_relevant, is_mine, v, fee = self.get_tx_value(tx, account)
1074                 if v is not None: balance += v
1075
1076             c, u = self.get_account_balance(account)
1077
1078             if balance != c+u:
1079                 result.append( ('', 1000, 0, c+u-balance, None, c+u-balance, None ) )
1080
1081             balance = c + u - balance
1082             for tx_hash, tx in history:
1083                 is_relevant, is_mine, value, fee = self.get_tx_value(tx, account)
1084                 if not is_relevant:
1085                     continue
1086                 if value is not None:
1087                     balance += value
1088
1089                 conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None)
1090                 result.append( (tx_hash, conf, is_mine, value, fee, balance, timestamp) )
1091
1092         return result
1093
1094
1095     def get_label(self, tx_hash):
1096         label = self.labels.get(tx_hash)
1097         is_default = (label == '') or (label is None)
1098         if is_default: label = self.get_default_label(tx_hash)
1099         return label, is_default
1100
1101
1102     def get_default_label(self, tx_hash):
1103         tx = self.transactions.get(tx_hash)
1104         default_label = ''
1105         if tx:
1106             is_relevant, is_mine, _, _ = self.get_tx_value(tx)
1107             if is_mine:
1108                 for o in tx.outputs:
1109                     o_addr, _ = o
1110                     if not self.is_mine(o_addr):
1111                         try:
1112                             default_label = self.labels[o_addr]
1113                         except KeyError:
1114                             default_label = o_addr
1115                         break
1116                 else:
1117                     default_label = '(internal)'
1118             else:
1119                 for o in tx.outputs:
1120                     o_addr, _ = o
1121                     if self.is_mine(o_addr) and not self.is_change(o_addr):
1122                         break
1123                 else:
1124                     for o in tx.outputs:
1125                         o_addr, _ = o
1126                         if self.is_mine(o_addr):
1127                             break
1128                     else:
1129                         o_addr = None
1130
1131                 if o_addr:
1132                     dest_label = self.labels.get(o_addr)
1133                     try:
1134                         default_label = self.labels[o_addr]
1135                     except KeyError:
1136                         default_label = o_addr
1137
1138         return default_label
1139
1140
1141     def make_unsigned_transaction(self, outputs, fee=None, change_addr=None, account=None ):
1142         for address, x in outputs:
1143             assert is_valid(address)
1144         amount = sum( map(lambda x:x[1], outputs) )
1145         inputs, total, fee = self.choose_tx_inputs( amount, fee, account )
1146         if not inputs:
1147             raise ValueError("Not enough funds")
1148         outputs = self.add_tx_change(inputs, outputs, amount, fee, total, change_addr, account)
1149         return Transaction.from_io(inputs, outputs)
1150
1151
1152     def mktx(self, outputs, password, fee=None, change_addr=None, account=None ):
1153         tx = self.make_unsigned_transaction(outputs, fee, change_addr, account)
1154         self.sign_transaction(tx, password)
1155         return tx
1156
1157
1158     def sign_transaction(self, tx, password):
1159         keypairs = {}
1160         for i, txin in enumerate(tx.inputs):
1161             address = txin['address']
1162             account, sequence = self.get_address_index(address)
1163             txin['KeyID'] = self.get_keyID(account, sequence)
1164             redeemScript = self.accounts[account].redeem_script(sequence)
1165             if redeemScript: 
1166                 txin['redeemScript'] = redeemScript
1167             else:
1168                 txin['redeemPubkey'] = self.accounts[account].get_pubkey(*sequence)
1169             private_keys = self.get_private_key(address, password)
1170             for sec in private_keys:
1171                 pubkey = public_key_from_private_key(sec)
1172                 keypairs[ pubkey ] = sec
1173         tx.sign(keypairs)
1174
1175
1176     def sendtx(self, tx):
1177         # synchronous
1178         h = self.send_tx(tx)
1179         self.tx_event.wait()
1180         return self.receive_tx(h)
1181
1182     def send_tx(self, tx):
1183         # asynchronous
1184         self.tx_event.clear()
1185         self.interface.send([('blockchain.transaction.broadcast', [str(tx)])], 'synchronizer')
1186         return tx.hash()
1187
1188     def receive_tx(self,tx_hash):
1189         out = self.tx_result 
1190         if out != tx_hash:
1191             return False, "error: " + out
1192         return True, out
1193
1194
1195
1196     def update_password(self, seed, old_password, new_password):
1197         if new_password == '': new_password = None
1198         # this will throw an exception if unicode cannot be converted
1199         self.seed = pw_encode( seed, new_password)
1200         self.storage.put('seed', self.seed, True)
1201         self.use_encryption = (new_password != None)
1202         self.storage.put('use_encryption', self.use_encryption,True)
1203         for k in self.imported_keys.keys():
1204             a = self.imported_keys[k]
1205             b = pw_decode(a, old_password)
1206             c = pw_encode(b, new_password)
1207             self.imported_keys[k] = c
1208         self.storage.put('imported_keys', self.imported_keys, True)
1209
1210         for k, v in self.master_private_keys.items():
1211             b = pw_decode(v, old_password)
1212             c = pw_encode(b, new_password)
1213             self.master_private_keys[k] = c
1214         self.storage.put('master_private_keys', self.master_private_keys, True)
1215
1216
1217     def freeze(self,addr):
1218         if self.is_mine(addr) and addr not in self.frozen_addresses:
1219             self.unprioritize(addr)
1220             self.frozen_addresses.append(addr)
1221             self.storage.put('frozen_addresses', self.frozen_addresses, True)
1222             return True
1223         else:
1224             return False
1225
1226     def unfreeze(self,addr):
1227         if self.is_mine(addr) and addr in self.frozen_addresses:
1228             self.frozen_addresses.remove(addr)
1229             self.storage.put('frozen_addresses', self.frozen_addresses, True)
1230             return True
1231         else:
1232             return False
1233
1234     def prioritize(self,addr):
1235         if self.is_mine(addr) and addr not in self.prioritized_addresses:
1236             self.unfreeze(addr)
1237             self.prioritized_addresses.append(addr)
1238             self.storage.put('prioritized_addresses', self.prioritized_addresses, True)
1239             return True
1240         else:
1241             return False
1242
1243     def unprioritize(self,addr):
1244         if self.is_mine(addr) and addr in self.prioritized_addresses:
1245             self.prioritized_addresses.remove(addr)
1246             self.storage.put('prioritized_addresses', self.prioritized_addresses, True)
1247             return True
1248         else:
1249             return False
1250
1251
1252     def set_verifier(self, verifier):
1253         self.verifier = verifier
1254
1255         # review transactions that are in the history
1256         for addr, hist in self.history.items():
1257             if hist == ['*']: continue
1258             for tx_hash, tx_height in hist:
1259                 if tx_height>0:
1260                     # add it in case it was previously unconfirmed
1261                     self.verifier.add(tx_hash, tx_height)
1262
1263
1264         # if we are on a pruning server, remove unverified transactions
1265         vr = self.verifier.transactions.keys() + self.verifier.verified_tx.keys()
1266         for tx_hash in self.transactions.keys():
1267             if tx_hash not in vr:
1268                 self.transactions.pop(tx_hash)
1269
1270
1271
1272     def check_new_history(self, addr, hist):
1273         
1274         # check that all tx in hist are relevant
1275         if hist != ['*']:
1276             for tx_hash, height in hist:
1277                 tx = self.transactions.get(tx_hash)
1278                 if not tx: continue
1279                 if not tx.has_address(addr):
1280                     return False
1281
1282         # check that we are not "orphaning" a transaction
1283         old_hist = self.history.get(addr,[])
1284         if old_hist == ['*']: return True
1285
1286         for tx_hash, height in old_hist:
1287             if tx_hash in map(lambda x:x[0], hist): continue
1288             found = False
1289             for _addr, _hist in self.history.items():
1290                 if _addr == addr: continue
1291                 if _hist == ['*']: continue
1292                 _tx_hist = map(lambda x:x[0], _hist)
1293                 if tx_hash in _tx_hist:
1294                     found = True
1295                     break
1296
1297             if not found:
1298                 tx = self.transactions.get(tx_hash)
1299                 # tx might not be there
1300                 if not tx: continue
1301                 
1302                 # already verified?
1303                 if self.verifier.get_height(tx_hash):
1304                     continue
1305                 # unconfirmed tx
1306                 print_error("new history is orphaning transaction:", tx_hash)
1307                 # check that all outputs are not mine, request histories
1308                 ext_requests = []
1309                 for _addr, _v in tx.outputs:
1310                     # assert not self.is_mine(_addr)
1311                     ext_requests.append( ('blockchain.address.get_history', [_addr]) )
1312
1313                 ext_h = self.interface.synchronous_get(ext_requests)
1314                 print_error("sync:", ext_requests, ext_h)
1315                 height = None
1316                 for h in ext_h:
1317                     if h == ['*']: continue
1318                     for item in h:
1319                         if item.get('tx_hash') == tx_hash:
1320                             height = item.get('height')
1321                 if height:
1322                     print_error("found height for", tx_hash, height)
1323                     self.verifier.add(tx_hash, height)
1324                 else:
1325                     print_error("removing orphaned tx from history", tx_hash)
1326                     self.transactions.pop(tx_hash)
1327
1328         return True
1329
1330
1331
1332     def check_new_tx(self, tx_hash, tx):
1333         # 1 check that tx is referenced in addr_history. 
1334         addresses = []
1335         for addr, hist in self.history.items():
1336             if hist == ['*']:continue
1337             for txh, height in hist:
1338                 if txh == tx_hash: 
1339                     addresses.append(addr)
1340
1341         if not addresses:
1342             return False
1343
1344         # 2 check that referencing addresses are in the tx
1345         for addr in addresses:
1346             if not tx.has_address(addr):
1347                 return False
1348
1349         return True
1350
1351
1352     def start_threads(self, network):
1353         from verifier import TxVerifier
1354         self.network = network
1355         self.interface = network.interface
1356         self.verifier = TxVerifier(self.network, self.storage)
1357         self.verifier.start()
1358         self.set_verifier(self.verifier)
1359         self.synchronizer = WalletSynchronizer(self)
1360         self.synchronizer.start()
1361
1362     def stop_threads(self):
1363         self.verifier.stop()
1364         self.synchronizer.stop()
1365
1366
1367
1368
1369
1370 class WalletSynchronizer(threading.Thread):
1371
1372
1373     def __init__(self, wallet):
1374         threading.Thread.__init__(self)
1375         self.daemon = True
1376         self.wallet = wallet
1377         wallet.synchronizer = self
1378         self.interface = self.wallet.interface
1379         self.interface.register_channel('synchronizer')
1380         #self.wallet.network.register_callback('connected', lambda: self.wallet.set_up_to_date(False))
1381         self.was_updated = True
1382         self.running = False
1383         self.lock = threading.Lock()
1384
1385     def stop(self):
1386         with self.lock: self.running = False
1387         self.interface.poke('synchronizer')
1388
1389     def is_running(self):
1390         with self.lock: return self.running
1391
1392     
1393     def subscribe_to_addresses(self, addresses):
1394         messages = []
1395         for addr in addresses:
1396             messages.append(('blockchain.address.subscribe', [addr]))
1397         self.interface.send( messages, 'synchronizer')
1398
1399
1400     def run(self):
1401         if not self.interface.is_connected:
1402             print_error( "synchronizer: waiting for interface")
1403             self.interface.connect_event.wait()
1404
1405         with self.lock: self.running = True
1406
1407         requested_tx = []
1408         missing_tx = []
1409         requested_histories = {}
1410
1411         # request any missing transactions
1412         for history in self.wallet.history.values():
1413             if history == ['*']: continue
1414             for tx_hash, tx_height in history:
1415                 if self.wallet.transactions.get(tx_hash) is None and (tx_hash, tx_height) not in missing_tx:
1416                     missing_tx.append( (tx_hash, tx_height) )
1417         print_error("missing tx", missing_tx)
1418
1419         # wait until we are connected, in case the user is not connected
1420         while not self.interface.is_connected:
1421             time.sleep(1)
1422         
1423         # subscriptions
1424         self.subscribe_to_addresses(self.wallet.addresses(True))
1425
1426         while self.is_running():
1427             # 1. create new addresses
1428             new_addresses = self.wallet.synchronize()
1429
1430             # request missing addresses
1431             if new_addresses:
1432                 self.subscribe_to_addresses(new_addresses)
1433
1434             # request missing transactions
1435             for tx_hash, tx_height in missing_tx:
1436                 if (tx_hash, tx_height) not in requested_tx:
1437                     self.interface.send([ ('blockchain.transaction.get',[tx_hash, tx_height]) ], 'synchronizer')
1438                     requested_tx.append( (tx_hash, tx_height) )
1439             missing_tx = []
1440
1441             # detect if situation has changed
1442             if not self.interface.is_up_to_date('synchronizer'):
1443                 if self.wallet.is_up_to_date():
1444                     self.wallet.set_up_to_date(False)
1445                     self.was_updated = True
1446             else:
1447                 if not self.wallet.is_up_to_date():
1448                     self.wallet.set_up_to_date(True)
1449                     self.was_updated = True
1450
1451             if self.was_updated:
1452                 self.interface.network.trigger_callback('updated')
1453                 self.was_updated = False
1454
1455             # 2. get a response
1456             r = self.interface.get_response('synchronizer')
1457
1458             # poke sends None. (needed during stop)
1459             if not r: continue
1460
1461             # 3. handle response
1462             method = r['method']
1463             params = r['params']
1464             result = r.get('result')
1465             error = r.get('error')
1466             if error:
1467                 print "error", r
1468                 continue
1469
1470             if method == 'blockchain.address.subscribe':
1471                 addr = params[0]
1472                 if self.wallet.get_status(self.wallet.get_history(addr)) != result:
1473                     if requested_histories.get(addr) is None:
1474                         self.interface.send([('blockchain.address.get_history', [addr])], 'synchronizer')
1475                         requested_histories[addr] = result
1476
1477             elif method == 'blockchain.address.get_history':
1478                 addr = params[0]
1479                 print_error("receiving history", addr, result)
1480                 if result == ['*']:
1481                     assert requested_histories.pop(addr) == '*'
1482                     self.wallet.receive_history_callback(addr, result)
1483                 else:
1484                     hist = []
1485                     # check that txids are unique
1486                     txids = []
1487                     for item in result:
1488                         tx_hash = item['tx_hash']
1489                         if tx_hash not in txids:
1490                             txids.append(tx_hash)
1491                             hist.append( (tx_hash, item['height']) )
1492
1493                     if len(hist) != len(result):
1494                         raise BaseException("error: server sent history with non-unique txid", result)
1495
1496                     # check that the status corresponds to what was announced
1497                     rs = requested_histories.pop(addr)
1498                     if self.wallet.get_status(hist) != rs:
1499                         raise BaseException("error: status mismatch: %s"%addr)
1500                 
1501                     # store received history
1502                     self.wallet.receive_history_callback(addr, hist)
1503
1504                     # request transactions that we don't have 
1505                     for tx_hash, tx_height in hist:
1506                         if self.wallet.transactions.get(tx_hash) is None:
1507                             if (tx_hash, tx_height) not in requested_tx and (tx_hash, tx_height) not in missing_tx:
1508                                 missing_tx.append( (tx_hash, tx_height) )
1509
1510             elif method == 'blockchain.transaction.get':
1511                 tx_hash = params[0]
1512                 tx_height = params[1]
1513                 assert tx_hash == hash_encode(Hash(result.decode('hex')))
1514                 tx = Transaction(result)
1515                 self.wallet.receive_tx_callback(tx_hash, tx, tx_height)
1516                 self.was_updated = True
1517                 requested_tx.remove( (tx_hash, tx_height) )
1518                 print_error("received tx:", tx_hash, len(tx.raw))
1519
1520             elif method == 'blockchain.transaction.broadcast':
1521                 self.wallet.tx_result = result
1522                 self.wallet.tx_event.set()
1523
1524             else:
1525                 print_error("Error: Unknown message:" + method + ", " + repr(params) + ", " + repr(result) )
1526
1527             if self.was_updated and not requested_tx:
1528                 self.interface.network.trigger_callback('updated')
1529                 self.interface.network.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
1530
1531                 self.was_updated = False