handle multiple accounts with separation
authorthomasv <thomasv@gitorious>
Fri, 5 Apr 2013 14:00:34 +0000 (16:00 +0200)
committerthomasv <thomasv@gitorious>
Sat, 6 Apr 2013 16:44:31 +0000 (18:44 +0200)
electrum
gui/gui_classic.py
lib/bitcoin.py
lib/commands.py
lib/wallet.py

index 15fcb60..00ce71c 100755 (executable)
--- a/electrum
+++ b/electrum
@@ -323,7 +323,8 @@ if __name__ == '__main__':
         args = [cmd, options.show_all, options.show_balance, options.show_labels]
 
     elif cmd in ['payto', 'mktx']:
-        args = [ 'mktx', args[1], Decimal(args[2]), Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, options.from_addr ]
+        domain = [options.from_addr] if options.from_addr else None
+        args = [ 'mktx', args[1], Decimal(args[2]), Decimal(options.tx_fee) if options.tx_fee else None, options.change_addr, domain ]
 
     elif cmd == 'help':
         if len(args) < 2:
index 4566504..ac317be 100644 (file)
@@ -285,8 +285,9 @@ class ElectrumWindow(QMainWindow):
         self.lite = None
         self.wallet = wallet
         self.config = config
-        self.init_plugins()
+        self.current_account = self.config.get("current_account", None)
 
+        self.init_plugins()
         self.create_status_bar()
 
         self.wallet.interface.register_callback('updated', lambda: self.emit(QtCore.SIGNAL('update_wallet')))
@@ -429,7 +430,7 @@ class ElectrumWindow(QMainWindow):
                 text = _("Synchronizing...")
                 icon = QIcon(":icons/status_waiting.png")
             else:
-                c, u = self.wallet.get_balance()
+                c, u = self.wallet.get_account_balance(self.current_account)
                 text =  _( "Balance" ) + ": %s "%( format_satoshis(c,False,self.wallet.num_zeros) )
                 if u: text +=  "[%s unconfirmed]"%( format_satoshis(u,True,self.wallet.num_zeros).strip() )
                 text += self.create_quote_text(Decimal(c+u)/100000000)
@@ -607,7 +608,7 @@ class ElectrumWindow(QMainWindow):
     def update_history_tab(self):
 
         self.history_list.clear()
-        for item in self.wallet.get_tx_history():
+        for item in self.wallet.get_tx_history(self.current_account):
             tx_hash, conf, is_mine, value, fee, balance, timestamp = item
             if conf:
                 try:
@@ -666,6 +667,7 @@ class ElectrumWindow(QMainWindow):
         grid.setColumnMinimumWidth(3,300)
         grid.setColumnStretch(5,1)
 
+
         self.payto_e = QLineEdit()
         grid.addWidget(QLabel(_('Pay to')), 1, 0)
         grid.addWidget(self.payto_e, 1, 1, 1, 3)
@@ -724,8 +726,8 @@ class ElectrumWindow(QMainWindow):
             self.funds_error = False
 
             if self.amount_e.text() == '!':
-                c, u = self.wallet.get_balance()
-                inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0 )
+                c, u = self.wallet.get_account_balance(self.current_account)
+                inputs, total, fee = self.wallet.choose_tx_inputs( c + u, 0, self.current_account)
                 fee = self.wallet.estimated_fee(inputs)
                 amount = c + u - fee
                 self.amount_e.setText( str( Decimal( amount ) / 100000000 ) )
@@ -737,7 +739,7 @@ class ElectrumWindow(QMainWindow):
             if not is_fee: fee = None
             if amount is None:
                 return
-            inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee )
+            inputs, total, fee = self.wallet.choose_tx_inputs( amount, fee, self.current_account )
             if not is_fee:
                 self.fee_e.setText( str( Decimal( fee ) / 100000000 ) )
             if inputs:
@@ -802,7 +804,7 @@ class ElectrumWindow(QMainWindow):
             return
 
         try:
-            tx = self.wallet.mktx( [(to_address, amount)], password, fee)
+            tx = self.wallet.mktx( [(to_address, amount)], password, fee, account=self.current_account)
         except BaseException, e:
             self.show_message(str(e))
             return
@@ -1091,8 +1093,16 @@ class ElectrumWindow(QMainWindow):
             for i,width in enumerate(self.column_widths['receive'][self.expert_mode]):
                 l.setColumnWidth(i, width)        
 
+        if self.current_account is None:
+            account_items = self.wallet.accounts.items()
+        elif self.current_account != -1:
+            account_items = [(self.current_account, self.wallet.accounts.get(self.current_account))]
+        else:
+            account_items = []
 
-        for k, account in self.wallet.accounts.items():
+        print self.current_account
+            
+        for k, account in account_items:
             name = account.get('name',str(k))
             c,u = self.wallet.get_account_balance(k)
             account_item = QTreeWidgetItem( [ name, '', format_satoshis(c+u), ''] )
@@ -1127,7 +1137,8 @@ class ElectrumWindow(QMainWindow):
                         item.setBackgroundColor(1, QColor('red'))
                     seq_item.addChild(item)
 
-        if self.wallet.imported_keys:
+
+        if self.wallet.imported_keys and (self.current_account is None or self.current_account == -1):
             c,u = self.wallet.get_imported_balance()
             account_item = QTreeWidgetItem( [ _('Imported'), '', format_satoshis(c+u), ''] )
             l.addTopLevelItem(account_item)
@@ -1183,6 +1194,17 @@ class ElectrumWindow(QMainWindow):
         console.updateNamespace(methods)
         return console
 
+    def change_account(self,s):
+        if s == _("All accounts"):
+            self.current_account = None
+        else:
+            accounts = self.wallet.get_accounts()
+            for k, v in accounts.items():
+                if v == s:
+                    self.current_account = k
+        self.update_history_tab()
+        self.update_status()
+        self.update_receive_tab()
 
     def create_status_bar(self):
         self.status_text = ""
@@ -1194,6 +1216,14 @@ class ElectrumWindow(QMainWindow):
         if(update_notification.new_version):
             sb.addPermanentWidget(update_notification)
 
+        accounts = self.wallet.get_accounts()
+        if len(accounts) > 1:
+            from_combo = QComboBox()
+            from_combo.addItems([_("All accounts")] + accounts.values())
+            from_combo.setCurrentIndex(0)
+            self.connect(from_combo,SIGNAL("activated(QString)"),self.change_account) 
+            sb.addPermanentWidget(from_combo)
+
         if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
             sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
         if self.wallet.seed:
index 41f9395..b7bc66b 100644 (file)
@@ -804,6 +804,7 @@ class Transaction:
 
     def get_value(self, addresses, prevout_values):
         # return the balance for that tx
+        is_relevant = False
         is_send = False
         is_pruned = False
         is_partial = False
@@ -813,6 +814,7 @@ class Transaction:
             addr = item.get('address')
             if addr in addresses:
                 is_send = True
+                is_relevant = True
                 key = item['prevout_hash']  + ':%d'%item['prevout_n']
                 value = prevout_values.get( key )
                 if value is None:
@@ -829,6 +831,7 @@ class Transaction:
             v_out += value
             if addr in addresses:
                 v_out_mine += value
+                is_relevant = True
 
         if is_pruned:
             # some inputs are mine:
@@ -850,7 +853,7 @@ class Transaction:
                 # all inputs are mine
                 fee = v_out - v_in
 
-        return is_send, v, fee
+        return is_relevant, is_send, v, fee
 
     def as_dict(self):
         import json
index 85c53ac..245820b 100644 (file)
@@ -208,7 +208,7 @@ class Commands:
             return False
 
 
-    def _mktx(self, to_address, amount, fee = None, change_addr = None, from_addr = None):
+    def _mktx(self, to_address, amount, fee = None, change_addr = None, domain = None):
 
         if not is_valid(to_address):
             raise BaseException("Invalid Bitcoin address", to_address)
@@ -217,12 +217,13 @@ class Commands:
             if not is_valid(change_addr):
                 raise BaseException("Invalid Bitcoin address", change_addr)
 
-        if from_addr:
-            if not is_valid(from_addr):
-                raise BaseException("invalid Bitcoin address", from_addr)
+        if domain is not None:
+            for addr in domain:
+                if not is_valid(addr):
+                    raise BaseException("invalid Bitcoin address", addr)
             
-            if not self.wallet.is_mine(from_addr):
-                raise BaseException("address not in wallet")
+                if not self.wallet.is_mine(addr):
+                    raise BaseException("address not in wallet", addr)
 
         for k, v in self.wallet.labels.items():
             if v == to_address:
@@ -234,16 +235,16 @@ class Commands:
 
         amount = int(100000000*amount)
         if fee: fee = int(100000000*fee)
-        return self.wallet.mktx( [(to_address, amount)], self.password, fee , change_addr, from_addr)
+        return self.wallet.mktx( [(to_address, amount)], self.password, fee , change_addr, domain)
 
 
-    def mktx(self, to_address, amount, fee = None, change_addr = None, from_addr = None):
-        tx = self._mktx(to_address, amount, fee, change_addr, from_addr)
+    def mktx(self, to_address, amount, fee = None, change_addr = None, domain = None):
+        tx = self._mktx(to_address, amount, fee, change_addr, domain)
         return tx.as_dict()
 
 
-    def payto(self, to_address, amount, fee = None, change_addr = None, from_addr = None):
-        tx = self._mktx(to_address, amount, fee, change_addr, from_addr)
+    def payto(self, to_address, amount, fee = None, change_addr = None, domain = None):
+        tx = self._mktx(to_address, amount, fee, change_addr, domain)
         r, h = self.wallet.sendtx( tx )
         return h
 
index 755f063..07c0f4c 100644 (file)
@@ -165,11 +165,10 @@ class Wallet:
         self.config.set_key('accounts', self.accounts, True)
 
 
-    def addresses(self, include_change = False):
-        o = self.imported_keys.keys()
-        for a in self.accounts.values():
-            o += a[0]
-            if include_change: o += a[1]
+    def addresses(self, include_change = True):
+        o = self.get_account_addresses(-1, include_change)
+        for a in self.accounts.keys():
+            o += self.get_account_addresses(a, include_change)
         return o
 
 
@@ -400,7 +399,7 @@ class Wallet:
 
     def fill_addressbook(self):
         for tx_hash, tx in self.transactions.items():
-            is_send, _, _ = self.get_tx_value(tx)
+            is_relevant, is_send, _, _ = self.get_tx_value(tx)
             if is_send:
                 for addr, v in tx.outputs:
                     if not self.is_mine(addr) and addr not in self.addressbook:
@@ -421,10 +420,9 @@ class Wallet:
         return flags
         
 
-    def get_tx_value(self, tx, addresses=None):
-        if addresses is None: addresses = self.addresses(True)
-        return tx.get_value(addresses, self.prevout_values)
-
+    def get_tx_value(self, tx, account=None):
+        domain = self.get_account_addresses(account)
+        return tx.get_value(domain, self.prevout_values)
 
     
     def update_tx_outputs(self, tx_hash):
@@ -480,9 +478,25 @@ class Wallet:
                 u += v
         return c, u
 
-    def get_account_addresses(self, a):
-        ac = self.accounts[a]
-        return ac[0] + ac[1]
+
+    def get_accounts(self):
+        accounts = {}
+        for k, account in self.accounts.items():
+            accounts[k] = account.get('name')
+        if self.imported_keys:
+            accounts[-1] = 'Imported keys'
+        return accounts
+
+    def get_account_addresses(self, a, include_change=True):
+        if a is None:
+            o = self.addresses(True)
+        elif a == -1:
+            o = self.imported_keys.keys()
+        else:
+            ac = self.accounts[a]
+            o = ac[0][:]
+            if include_change: o += ac[1]
+        return o
 
     def get_imported_balance(self):
         cc = uu = 0
@@ -493,6 +507,11 @@ class Wallet:
         return cc, uu
 
     def get_account_balance(self, account):
+        if account is None:
+            return self.get_balance()
+        elif account == -1:
+            return self.get_imported_balance()
+        
         conf = unconf = 0
         for addr in self.get_account_addresses(account): 
             c, u = self.get_addr_balance(addr)
@@ -531,14 +550,13 @@ class Wallet:
 
 
 
-    def choose_tx_inputs( self, amount, fixed_fee, from_addr = None ):
+    def choose_tx_inputs( self, amount, fixed_fee, account = None ):
         """ todo: minimize tx size """
         total = 0
         fee = self.fee if fixed_fee is None else fixed_fee
-
+        domain = self.get_account_addresses(account)
         coins = []
         prioritized_coins = []
-        domain = [from_addr] if from_addr else self.addresses(True)
         for i in self.frozen_addresses:
             if i in domain: domain.remove(i)
 
@@ -571,13 +589,17 @@ class Wallet:
         return fee
 
 
-    def add_tx_change( self, outputs, amount, fee, total, change_addr=None ):
+    def add_tx_change( self, inputs, outputs, amount, fee, total, change_addr=None, account=0 ):
+        "add change to a transaction"
         change_amount = total - ( amount + fee )
         if change_amount != 0:
-            # normally, the update thread should ensure that the last change address is unused
             if not change_addr:
-                change_addresses = self.accounts[0][1]
-                change_addr = change_addresses[-self.gap_limit_for_change]
+                if not self.use_change or account == -1:
+                    change_addr = inputs[-1]['address']
+                else:
+                    if account is None: account = 0
+                    change_addr = self.accounts[account][1][-self.gap_limit_for_change]
+
             # Insert the change output at a random position in the outputs
             posn = random.randint(0, len(outputs))
             outputs[posn:posn] = [( change_addr,  change_amount)]
@@ -588,6 +610,7 @@ class Wallet:
         with self.lock:
             return self.history.get(address)
 
+
     def get_status(self, h):
         if not h: return None
         if h == ['*']: return '*'
@@ -597,10 +620,8 @@ class Wallet:
         return hashlib.sha256( status ).digest().encode('hex')
 
 
-
     def receive_tx_callback(self, tx_hash, tx, tx_height):
 
-
         if not self.check_new_tx(tx_hash, tx):
             # may happen due to pruning
             print_error("received transaction that is no longer referenced in history", tx_hash)
@@ -631,7 +652,7 @@ class Wallet:
                     if self.verifier: self.verifier.add(tx_hash, tx_height)
 
 
-    def get_tx_history(self):
+    def get_tx_history(self, account=None):
         with self.transaction_lock:
             history = self.transactions.items()
             history.sort(key = lambda x: self.verifier.verified_tx.get(x[0]) if self.verifier.verified_tx.get(x[0]) else (1e12,0,0))
@@ -639,21 +660,23 @@ class Wallet:
     
             balance = 0
             for tx_hash, tx in history:
-                is_mine, v, fee = self.get_tx_value(tx)
+                is_relevant, is_mine, v, fee = self.get_tx_value(tx, account)
                 if v is not None: balance += v
-            c, u = self.get_balance()
+
+            c, u = self.get_account_balance(account)
 
             if balance != c+u:
-                #v_str = format_satoshis( c+u - balance, True, self.num_zeros)
                 result.append( ('', 1000, 0, c+u-balance, None, c+u-balance, None ) )
 
             balance = c + u - balance
             for tx_hash, tx in history:
-                conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None)
-                is_mine, value, fee = self.get_tx_value(tx)
+                is_relevant, is_mine, value, fee = self.get_tx_value(tx, account)
+                if not is_relevant:
+                    continue
                 if value is not None:
                     balance += value
 
+                conf, timestamp = self.verifier.get_confirmations(tx_hash) if self.verifier else (None, None)
                 result.append( (tx_hash, conf, is_mine, value, fee, balance, timestamp) )
 
         return result
@@ -670,7 +693,7 @@ class Wallet:
         tx = self.transactions.get(tx_hash)
         default_label = ''
         if tx:
-            is_mine, _, _ = self.get_tx_value(tx)
+            is_relevant, is_mine, _, _ = self.get_tx_value(tx)
             if is_mine:
                 for o in tx.outputs:
                     o_addr, _ = o
@@ -705,20 +728,27 @@ class Wallet:
         return default_label
 
 
-    def mktx(self, outputs, password, fee=None, change_addr=None, from_addr= None):
-
+    def mktx(self, outputs, password, fee=None, change_addr=None, account=None ):
+        """
+        create a transaction
+        account parameter:
+           None means use all accounts
+           -1 means imported keys
+           0, 1, etc are seed accounts
+        """
+        
         for address, x in outputs:
             assert is_valid(address)
 
         amount = sum( map(lambda x:x[1], outputs) )
-        inputs, total, fee = self.choose_tx_inputs( amount, fee, from_addr )
+        
+        domain = self.get_account_addresses(account)
+            
+        inputs, total, fee = self.choose_tx_inputs( amount, fee, domain )
         if not inputs:
             raise ValueError("Not enough funds")
 
-        if not self.use_change and not change_addr:
-            change_addr = inputs[-1]['address']
-            print_error( "Sending change to", change_addr )
-        outputs = self.add_tx_change(outputs, amount, fee, total, change_addr)
+        outputs = self.add_tx_change(inputs, outputs, amount, fee, total, change_addr, account)
 
         tx = Transaction.from_io(inputs, outputs)
 
@@ -726,7 +756,7 @@ class Wallet:
         for i in range(len(tx.inputs)):
             txin = tx.inputs[i]
             address = txin['address']
-            if address in self.imported_keys.keys(): 
+            if address in self.imported_keys.keys():
                 pk_addresses.append(address)
                 continue
             account, sequence = self.get_address_index(address)