~ubuntu-branches/debian/stretch/electrum/stretch

« back to all changes in this revision

Viewing changes to lib/wallet.py

  • Committer: Package Import Robot
  • Author(s): Tristan Seligmann
  • Date: 2015-10-23 21:00:40 UTC
  • mfrom: (1.1.6)
  • Revision ID: package-import@ubuntu.com-20151023210040-4ej97tguu585zwlc
Tags: 2.5.1-1
New upstream release.

Show diffs side-by-side

added added

removed removed

Lines of Context:
16
16
# You should have received a copy of the GNU General Public License
17
17
# along with this program. If not, see <http://www.gnu.org/licenses/>.
18
18
 
19
 
import sys
20
19
import os
21
20
import hashlib
22
21
import ast
23
22
import threading
24
23
import random
25
24
import time
26
 
import math
27
25
import json
28
26
import copy
29
27
from operator import itemgetter
30
28
 
31
 
from util import print_msg, print_error, NotEnoughFunds
32
 
from util import profiler
 
29
from util import NotEnoughFunds, PrintError, profiler
33
30
 
34
31
from bitcoin import *
35
32
from account import *
38
35
from transaction import Transaction
39
36
from plugins import run_hook
40
37
import bitcoin
41
 
from synchronizer import WalletSynchronizer
 
38
from synchronizer import Synchronizer
42
39
from mnemonic import Mnemonic
43
40
 
44
41
import paymentrequest
45
42
 
46
 
 
47
 
 
48
43
# internal ID for imported account
49
44
IMPORTED_ACCOUNT = '/x'
50
45
 
51
46
 
52
 
class WalletStorage(object):
 
47
class WalletStorage(PrintError):
53
48
 
54
49
    def __init__(self, path):
55
50
        self.lock = threading.RLock()
56
51
        self.data = {}
57
52
        self.path = path
58
53
        self.file_exists = False
59
 
        print_error( "wallet path", self.path )
 
54
        self.modified = False
 
55
        self.print_error("wallet path", self.path)
60
56
        if self.path:
61
57
            self.read(self.path)
62
58
 
87
83
                    json.dumps(key)
88
84
                    json.dumps(value)
89
85
                except:
90
 
                    print_error('Failed to convert label to json format', key)
 
86
                    self.print_error('Failed to convert label to json format', key)
91
87
                    continue
92
88
                self.data[key] = value
93
89
        self.file_exists = True
99
95
                v = default
100
96
            else:
101
97
                v = copy.deepcopy(v)
102
 
            return v
 
98
        return v
103
99
 
104
100
    def put(self, key, value, save = True):
105
101
        try:
106
102
            json.dumps(key)
107
103
            json.dumps(value)
108
104
        except:
109
 
            print_error("json error: cannot save", key)
 
105
            self.print_error("json error: cannot save", key)
110
106
            return
111
107
        with self.lock:
112
108
            if value is not None:
113
 
                self.data[key] = copy.deepcopy(value)
 
109
                if self.data.get(key) != value:
 
110
                    self.modified = True
 
111
                    self.data[key] = copy.deepcopy(value)
114
112
            elif key in self.data:
 
113
                self.modified = True
115
114
                self.data.pop(key)
116
115
            if save:
117
116
                self.write()
118
117
 
119
118
    def write(self):
120
119
        assert not threading.currentThread().isDaemon()
 
120
        if not self.modified:
 
121
            return
 
122
        s = json.dumps(self.data, indent=4, sort_keys=True)
121
123
        temp_path = "%s.tmp.%s" % (self.path, os.getpid())
122
 
        s = json.dumps(self.data, indent=4, sort_keys=True)
123
124
        with open(temp_path, "w") as f:
124
125
            f.write(s)
125
126
            f.flush()
133
134
        if 'ANDROID_DATA' not in os.environ:
134
135
            import stat
135
136
            os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE)
136
 
 
137
 
 
138
 
 
139
 
class Abstract_Wallet(object):
 
137
        self.print_error("saved")
 
138
 
 
139
 
 
140
 
 
141
class Abstract_Wallet(PrintError):
140
142
    """
141
143
    Wallet classes are created to handle various address generation methods.
142
144
    Completion states (watching-only, single account, no seed, etc) are handled inside classes.
171
173
 
172
174
        # spv
173
175
        self.verifier = None
174
 
        # Transactions pending verification.  Each value is the transaction height.  Access with self.lock.
 
176
        # Transactions pending verification.  A map from tx hash to transaction
 
177
        # height.  Access is not contended so no lock is needed.
175
178
        self.unverified_tx = {}
176
179
        # Verified transactions.  Each value is a (height, timestamp, block_pos) tuple.  Access with self.lock.
177
180
        self.verified_tx   = storage.get('verified_tx3',{})
190
193
        if self.storage.get('wallet_type') is None:
191
194
            self.storage.put('wallet_type', self.wallet_type, True)
192
195
 
 
196
    def diagnostic_name(self):
 
197
        return self.basename()
 
198
 
193
199
    @profiler
194
200
    def load_transactions(self):
195
201
        self.txi = self.storage.get('txi', {})
201
207
            tx = Transaction(raw)
202
208
            self.transactions[tx_hash] = tx
203
209
            if self.txi.get(tx_hash) is None and self.txo.get(tx_hash) is None and (tx_hash not in self.pruned_txo.values()):
204
 
                print_error("removing unreferenced tx", tx_hash)
 
210
                self.print_error("removing unreferenced tx", tx_hash)
205
211
                self.transactions.pop(tx_hash)
206
212
 
207
213
    @profiler
290
296
                except:
291
297
                    pass
292
298
            else:
293
 
                print_error("cannot load account", v)
 
299
                self.print_error("cannot load account", v)
294
300
 
295
301
    def synchronize(self):
296
302
        pass
364
370
                changed = True
365
371
 
366
372
        if changed:
 
373
            run_hook('set_label', self, name, text)
367
374
            self.storage.put('labels', self.labels, True)
368
375
 
369
 
        run_hook('set_label', name, text, changed)
370
376
        return changed
371
377
 
372
378
    def addresses(self, include_change = True):
416
422
        return decrypted
417
423
 
418
424
    def add_unverified_tx(self, tx_hash, tx_height):
419
 
        if tx_height > 0:
420
 
            with self.lock:
421
 
                self.unverified_tx[tx_hash] = tx_height
 
425
        # Only add if confirmed and not verified
 
426
        if tx_height > 0 and tx_hash not in self.verified_tx:
 
427
            self.unverified_tx[tx_hash] = tx_height
422
428
 
423
429
    def add_verified_tx(self, tx_hash, info):
 
430
        # Remove from the unverified map and add to the verified map and
 
431
        self.unverified_tx.pop(tx_hash, None)
424
432
        with self.lock:
425
433
            self.verified_tx[tx_hash] = info  # (tx_height, timestamp, pos)
426
434
        self.storage.put('verified_tx3', self.verified_tx, True)
429
437
        self.network.trigger_callback('verified', (tx_hash, conf, timestamp))
430
438
 
431
439
    def get_unverified_txs(self):
432
 
        '''Returns a list of tuples (tx_hash, height) that are unverified and not beyond local height'''
433
 
        txs = []
434
 
        with self.lock:
435
 
            for tx_hash, tx_height in self.unverified_tx.items():
436
 
                # do not request merkle branch before headers are available
437
 
                if tx_hash not in self.verified_tx and tx_height <= self.get_local_height():
438
 
                    txs.append((tx_hash, tx_height))
439
 
        return txs
 
440
        '''Returns a map from tx hash to transaction height'''
 
441
        return self.unverified_tx
440
442
 
441
443
    def undo_verifications(self, height):
442
444
        '''Used by the verifier when a reorg has happened'''
473
475
        "return position, even if the tx is unverified"
474
476
        with self.lock:
475
477
            x = self.verified_tx.get(tx_hash)
476
 
            y = self.unverified_tx.get(tx_hash)
 
478
        y = self.unverified_tx.get(tx_hash)
477
479
        if x:
478
480
            height, timestamp, pos = x
479
481
            return height, pos
692
694
        for addr, l in dd.items():
693
695
            for n, v, is_cb in l:
694
696
                if n == prevout_n:
695
 
                    print_error("found pay-to-pubkey address:", addr)
 
697
                    self.print_error("found pay-to-pubkey address:", addr)
696
698
                    return addr
697
699
 
698
700
    def add_transaction(self, tx_hash, tx):
748
750
 
749
751
    def remove_transaction(self, tx_hash):
750
752
        with self.transaction_lock:
751
 
            print_error("removing tx from history", tx_hash)
 
753
            self.print_error("removing tx from history", tx_hash)
752
754
            #tx = self.transactions.pop(tx_hash)
753
755
            for ser, hh in self.pruned_txo.items():
754
756
                if hh == tx_hash:
767
769
                        dd.pop(addr)
768
770
                    else:
769
771
                        dd[addr] = l
770
 
            self.txi.pop(tx_hash)
771
 
            self.txo.pop(tx_hash)
772
 
 
 
772
            try:
 
773
                self.txi.pop(tx_hash)
 
774
                self.txo.pop(tx_hash)
 
775
            except KeyErrror:
 
776
                self.print_error("tx was not in history", tx_hash)
773
777
 
774
778
    def receive_tx_callback(self, tx_hash, tx, tx_height):
775
779
        self.add_transaction(tx_hash, tx)
844
848
 
845
849
        # fixme: this may happen if history is incomplete
846
850
        if balance not in [None, 0]:
847
 
            print_error("Error: history not synchronized")
 
851
            self.print_error("Error: history not synchronized")
848
852
            return []
849
853
 
850
854
        return h2
933
937
                    fee = fixed_fee if fixed_fee is not None else self.estimated_fee(tx, fee_per_kb)
934
938
                    continue
935
939
                break
936
 
        print_error("using %d inputs"%len(tx.inputs))
 
940
        self.print_error("using %d inputs"%len(tx.inputs))
937
941
 
938
942
        # change address
939
943
        if not change_addr:
966
970
            change_amount = total - ( amount + fee )
967
971
            if change_amount > DUST_THRESHOLD:
968
972
                tx.outputs.append(('address', change_addr, change_amount))
969
 
                print_error('change', change_amount)
 
973
                self.print_error('change', change_amount)
970
974
            else:
971
 
                print_error('not keeping dust', change_amount)
 
975
                self.print_error('not keeping dust', change_amount)
972
976
        else:
973
 
            print_error('not keeping dust', change_amount)
 
977
            self.print_error('not keeping dust', change_amount)
974
978
 
975
979
        # Sort the inputs and outputs deterministically
976
980
        tx.BIP_LI01_sort()
977
981
 
978
 
        run_hook('make_unsigned_transaction', tx)
 
982
        run_hook('make_unsigned_transaction', self, tx)
979
983
        return tx
980
984
 
981
985
    def mktx(self, outputs, password, config, fee=None, change_addr=None, domain=None):
1023
1027
        # Sign
1024
1028
        if keypairs:
1025
1029
            tx.sign(keypairs)
1026
 
        run_hook('sign_transaction', tx, password)
 
1030
        # Run hook, and raise if error
 
1031
        tx.error = None
 
1032
        run_hook('sign_transaction', self, tx, password)
 
1033
        if tx.error:
 
1034
            raise BaseException(tx.error)
 
1035
 
1027
1036
 
1028
1037
    def sendtx(self, tx):
1029
1038
        # synchronous
1086
1095
            return True
1087
1096
        return False
1088
1097
 
1089
 
    def set_verifier(self, verifier):
1090
 
        self.verifier = verifier
1091
 
 
 
1098
    def prepare_for_verifier(self):
1092
1099
        # review transactions that are in the history
1093
1100
        for addr, hist in self.history.items():
1094
1101
            for tx_hash, tx_height in hist:
1100
1107
            vr = self.verified_tx.keys() + self.unverified_tx.keys()
1101
1108
        for tx_hash in self.transactions.keys():
1102
1109
            if tx_hash not in vr:
1103
 
                print_error("removing transaction", tx_hash)
 
1110
                self.print_error("removing transaction", tx_hash)
1104
1111
                self.transactions.pop(tx_hash)
1105
1112
 
1106
1113
    def start_threads(self, network):
1107
1114
        from verifier import SPV
1108
1115
        self.network = network
1109
1116
        if self.network is not None:
 
1117
            self.prepare_for_verifier()
1110
1118
            self.verifier = SPV(self.network, self)
1111
 
            self.verifier.start()
1112
 
            self.set_verifier(self.verifier)
1113
 
            self.synchronizer = WalletSynchronizer(self, network)
1114
 
            network.jobs.append(self.synchronizer.main_loop)
 
1119
            self.synchronizer = Synchronizer(self, network)
 
1120
            network.add_jobs([self.verifier, self.synchronizer])
1115
1121
        else:
1116
1122
            self.verifier = None
1117
1123
            self.synchronizer = None
1118
1124
 
1119
1125
    def stop_threads(self):
1120
1126
        if self.network:
1121
 
            self.verifier.stop()
1122
 
            self.network.jobs.remove(self.synchronizer.main_loop)
 
1127
            self.network.remove_jobs([self.synchronizer, self.verifier])
1123
1128
            self.synchronizer = None
 
1129
            self.verifier = None
1124
1130
            self.storage.put('stored_height', self.get_local_height(), True)
1125
1131
 
1126
1132
    def restore(self, cb):
1744
1750
                self.next_account = self.get_next_account(None)
1745
1751
                self.storage.put('next_account2', self.next_account)
1746
1752
            except:
1747
 
                print_error('cannot get next account')
 
1753
                self.print_error('cannot get next account')
1748
1754
        # check pending account
1749
1755
        if self.next_account is not None:
1750
1756
            next_id, next_xpub, next_pubkey, next_address = self.next_account
1751
1757
            if self.address_is_old(next_address):
1752
 
                print_error("creating account", next_id)
 
1758
                self.print_error("creating account", next_id)
1753
1759
                self.add_account(next_id, BIP32_Account({'xpub':next_xpub}))
1754
1760
                # here the user should get a notification
1755
1761
                self.next_account = None
1756
1762
                self.storage.put('next_account2', self.next_account)
1757
1763
            elif self.history.get(next_address, []):
1758
1764
                if next_id not in self.accounts:
1759
 
                    print_error("create pending account", next_id)
 
1765
                    self.print_error("create pending account", next_id)
1760
1766
                    self.accounts[next_id] = PendingAccount({'pending':True, 'address':next_address, 'pubkey':next_pubkey})
1761
1767
                    self.save_accounts()
1762
1768
 
1924
1930
 
1925
1931
        wallet_type = storage.get('wallet_type')
1926
1932
        if wallet_type:
1927
 
            for cat, t, name, c in wallet_types:
 
1933
            for cat, t, name, loader in wallet_types:
1928
1934
                if t == wallet_type:
1929
 
                    WalletClass = c
 
1935
                    if cat in ['hardware', 'twofactor']:
 
1936
                        WalletClass = lambda storage: apply(loader().constructor, (storage,))
 
1937
                    else:
 
1938
                        WalletClass = loader
1930
1939
                    break
1931
1940
            else:
1932
1941
                if re.match('(\d+)of(\d+)', wallet_type):