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