@ -25,6 +25,7 @@ import time
import json
import json
import copy
import copy
from functools import partial
from functools import partial
from i18n import _
from util import NotEnoughFunds , PrintError , profiler
from util import NotEnoughFunds , PrintError , profiler
@ -291,6 +292,7 @@ class Abstract_Wallet(PrintError):
def load_accounts ( self ) :
def load_accounts ( self ) :
self . accounts = { }
self . accounts = { }
d = self . storage . get ( ' accounts ' , { } )
d = self . storage . get ( ' accounts ' , { } )
removed = False
for k , v in d . items ( ) :
for k , v in d . items ( ) :
if self . wallet_type == ' old ' and k in [ 0 , ' 0 ' ] :
if self . wallet_type == ' old ' and k in [ 0 , ' 0 ' ] :
v [ ' mpk ' ] = self . storage . get ( ' master_public_key ' )
v [ ' mpk ' ] = self . storage . get ( ' master_public_key ' )
@ -300,12 +302,11 @@ class Abstract_Wallet(PrintError):
elif v . get ( ' xpub ' ) :
elif v . get ( ' xpub ' ) :
self . accounts [ k ] = BIP32_Account ( v )
self . accounts [ k ] = BIP32_Account ( v )
elif v . get ( ' pending ' ) :
elif v . get ( ' pending ' ) :
try :
removed = True
self . accounts [ k ] = PendingAccount ( v )
except :
pass
else :
else :
self . print_error ( " cannot load account " , v )
self . print_error ( " cannot load account " , v )
if removed :
self . save_accounts ( )
def synchronize ( self ) :
def synchronize ( self ) :
pass
pass
@ -313,8 +314,17 @@ class Abstract_Wallet(PrintError):
def can_create_accounts ( self ) :
def can_create_accounts ( self ) :
return False
return False
def set_up_to_date ( self , b ) :
def needs_next_account ( self ) :
with self . lock : self . up_to_date = b
return self . can_create_accounts ( ) and self . accounts_all_used ( )
def permit_account_naming ( self ) :
return self . can_create_accounts ( )
def set_up_to_date ( self , up_to_date ) :
with self . lock :
self . up_to_date = up_to_date
if up_to_date :
self . save_transactions ( write = True )
def is_up_to_date ( self ) :
def is_up_to_date ( self ) :
with self . lock : return self . up_to_date
with self . lock : return self . up_to_date
@ -645,15 +655,6 @@ class Abstract_Wallet(PrintError):
amount = max ( 0 , sendable - fee )
amount = max ( 0 , sendable - fee )
return amount , fee
return amount , fee
def get_account_name ( self , k ) :
return self . labels . get ( k , self . accounts [ k ] . get_name ( k ) )
def get_account_names ( self ) :
account_names = { }
for k in self . accounts . keys ( ) :
account_names [ k ] = self . get_account_name ( k )
return account_names
def get_account_addresses ( self , acc_id , include_change = True ) :
def get_account_addresses ( self , acc_id , include_change = True ) :
if acc_id is None :
if acc_id is None :
addr_list = self . addresses ( include_change )
addr_list = self . addresses ( include_change )
@ -1101,7 +1102,6 @@ class Abstract_Wallet(PrintError):
self . storage . write ( )
self . storage . write ( )
def wait_until_synchronized ( self , callback = None ) :
def wait_until_synchronized ( self , callback = None ) :
from i18n import _
def wait_for_wallet ( ) :
def wait_for_wallet ( ) :
self . set_up_to_date ( False )
self . set_up_to_date ( False )
while not self . is_up_to_date ( ) :
while not self . is_up_to_date ( ) :
@ -1125,8 +1125,19 @@ class Abstract_Wallet(PrintError):
else :
else :
self . synchronize ( )
self . synchronize ( )
def accounts_to_show ( self ) :
return self . accounts . keys ( )
def get_accounts ( self ) :
def get_accounts ( self ) :
return self . accounts
return { a_id : a for a_id , a in self . accounts . items ( )
if a_id in self . accounts_to_show ( ) }
def get_account_name ( self , k ) :
return self . labels . get ( k , self . accounts [ k ] . get_name ( k ) )
def get_account_names ( self ) :
ids = self . accounts_to_show ( )
return dict ( zip ( ids , map ( self . get_account_name , ids ) ) )
def add_account ( self , account_id , account ) :
def add_account ( self , account_id , account ) :
self . accounts [ account_id ] = account
self . accounts [ account_id ] = account
@ -1632,111 +1643,66 @@ class BIP32_Simple_Wallet(BIP32_Wallet):
class BIP32_HD_Wallet ( BIP32_Wallet ) :
class BIP32_HD_Wallet ( BIP32_Wallet ) :
# wallet that can create accounts
# wallet that can create accounts
def __init__ ( self , storage ) :
def __init__ ( self , storage ) :
self . next_account = storage . get ( ' next_account2 ' , None )
BIP32_Wallet . __init__ ( self , storage )
BIP32_Wallet . __init__ ( self , storage )
# Backwards-compatibility. Remove legacy "next_account2" and
# drop unused master public key to avoid duplicate errors
storage . put ( ' next_account2 ' , None )
self . master_public_keys . pop ( self . next_derivation ( ) [ 0 ] , None )
def next_account_number ( self ) :
assert ( set ( self . accounts . keys ( ) ) ==
set ( [ ' %d ' % n for n in range ( len ( self . accounts ) ) ] ) )
return len ( self . accounts )
def next_derivation ( self ) :
account_id = ' %d ' % self . next_account_number ( )
return self . root_name + account_id + " ' " , account_id
def show_account ( self , account_id ) :
return self . account_is_used ( account_id ) or account_id in self . labels
def last_account_id ( self ) :
return ' %d ' % ( self . next_account_number ( ) - 1 )
def accounts_to_show ( self ) :
# The last account is shown only if named or used
result = list ( self . accounts . keys ( ) )
last_id = self . last_account_id ( )
if not self . show_account ( last_id ) :
result . remove ( last_id )
return result
def can_create_accounts ( self ) :
def can_create_accounts ( self ) :
return self . root_name in self . master_private_keys . keys ( )
return self . root_name in self . master_private_keys . keys ( )
def addresses ( self , b = True ) :
def permit_account_naming ( self ) :
l = BIP32_Wallet . addresses ( self , b )
return ( self . can_create_accounts ( ) and
if self . next_account :
not self . show_account ( self . last_account_id ( ) ) )
_ , _ , _ , next_address = self . next_account
if next_address not in l :
l . append ( next_address )
return l
def get_address_index ( self , address ) :
if self . next_account :
next_id , next_xpub , next_pubkey , next_address = self . next_account
if address == next_address :
return next_id , ( 0 , 0 )
return BIP32_Wallet . get_address_index ( self , address )
def num_accounts ( self ) :
keys = [ ]
for k , v in self . accounts . items ( ) :
if type ( v ) != BIP32_Account :
continue
keys . append ( k )
i = 0
while True :
account_id = ' %d ' % i
if account_id not in keys :
break
i + = 1
return i
def get_next_account ( self , password ) :
account_id = ' %d ' % self . num_accounts ( )
derivation = self . root_name + " %d ' " % int ( account_id )
xpub , xprv = self . derive_xkeys ( self . root_name , derivation , password )
self . add_master_public_key ( derivation , xpub )
if xprv :
self . add_master_private_key ( derivation , xprv , password )
account = BIP32_Account ( { ' xpub ' : xpub } )
addr , pubkey = account . first_address ( )
self . add_address ( addr )
return account_id , xpub , pubkey , addr
def create_main_account ( self , password ) :
def create_main_account ( self , password ) :
# First check the password is valid (this raises if it isn't).
# First check the password is valid (this raises if it isn't).
self . check_password ( password )
self . check_password ( password )
assert self . num_accounts ( ) == 0
assert self . next_account_number ( ) == 0
self . create_account ( ' Main account ' , password )
self . create_next_account ( password , _ ( ' Main account ' ) )
self . create_next_account ( password )
def create_account ( self , name , password ) :
def create_next_account ( self , password , label = None ) :
account_id , xpub , _ , _ = self . get_next_account ( password )
derivation , account_id = self . next_derivation ( )
xpub , xprv = self . derive_xkeys ( self . root_name , derivation , password )
self . add_master_public_key ( derivation , xpub )
if xprv :
self . add_master_private_key ( derivation , xprv , password )
account = BIP32_Account ( { ' xpub ' : xpub } )
account = BIP32_Account ( { ' xpub ' : xpub } )
self . add_account ( account_id , account )
self . add_account ( account_id , account )
self . set_label ( account_id , name )
if label :
# add address of the next account
self . set_label ( account_id , label )
self . next_account = self . get_next_account ( password )
self . storage . put ( ' next_account2 ' , self . next_account )
def account_is_pending ( self , k ) :
return type ( self . accounts . get ( k ) ) == PendingAccount
def delete_pending_account ( self , k ) :
assert type ( self . accounts . get ( k ) ) == PendingAccount
self . accounts . pop ( k )
self . save_accounts ( )
self . save_accounts ( )
def create_pending_account ( self , name , password ) :
def account_is_used ( self , account_id ) :
if self . next_account is None :
return self . accounts [ account_id ] . is_used ( self )
self . next_account = self . get_next_account ( password )
self . storage . put ( ' next_account2 ' , self . next_account )
next_id , next_xpub , next_pubkey , next_address = self . next_account
if name :
self . set_label ( next_id , name )
self . accounts [ next_id ] = PendingAccount ( { ' pending ' : True , ' address ' : next_address , ' pubkey ' : next_pubkey } )
self . save_accounts ( )
def synchronize ( self ) :
# synchronize existing accounts
BIP32_Wallet . synchronize ( self )
if self . next_account is None and not self . use_encryption :
try :
self . next_account = self . get_next_account ( None )
self . storage . put ( ' next_account2 ' , self . next_account )
except :
self . print_error ( ' cannot get next account ' )
# check pending account
if self . next_account is not None :
next_id , next_xpub , next_pubkey , next_address = self . next_account
if self . address_is_old ( next_address ) :
self . print_error ( " creating account " , next_id )
self . add_account ( next_id , BIP32_Account ( { ' xpub ' : next_xpub } ) )
# here the user should get a notification
self . next_account = None
self . storage . put ( ' next_account2 ' , self . next_account )
elif self . history . get ( next_address , [ ] ) :
if next_id not in self . accounts :
self . print_error ( " create pending account " , next_id )
self . accounts [ next_id ] = PendingAccount ( { ' pending ' : True , ' address ' : next_address , ' pubkey ' : next_pubkey } )
self . save_accounts ( )
def accounts_all_used ( self ) :
return all ( self . account_is_used ( acc_id ) for acc_id in self . accounts )
class NewWallet ( BIP32_Wallet , Mnemonic ) :
class NewWallet ( BIP32_Wallet , Mnemonic ) :