Merge branch '1.9' of git://github.com/spesmilo/electrum into 1.9
This commit is contained in:
commit
9d1c31255c
47
electrum
47
electrum
|
@ -107,7 +107,7 @@ if __name__ == '__main__':
|
||||||
util.check_windows_wallet_migration()
|
util.check_windows_wallet_migration()
|
||||||
|
|
||||||
config = SimpleConfig(config_options)
|
config = SimpleConfig(config_options)
|
||||||
|
storage = WalletStorage(config)
|
||||||
|
|
||||||
if len(args)==0:
|
if len(args)==0:
|
||||||
url = None
|
url = None
|
||||||
|
@ -133,10 +133,14 @@ if __name__ == '__main__':
|
||||||
interface.start(wait = False)
|
interface.start(wait = False)
|
||||||
interface.send([('server.peers.subscribe',[])])
|
interface.send([('server.peers.subscribe',[])])
|
||||||
|
|
||||||
gui = gui.ElectrumGui(config,interface)
|
blockchain = BlockchainVerifier(interface, config)
|
||||||
|
blockchain.start()
|
||||||
|
|
||||||
|
gui = gui.ElectrumGui(config, interface, blockchain)
|
||||||
gui.main(url)
|
gui.main(url)
|
||||||
|
|
||||||
interface.stop()
|
interface.stop()
|
||||||
|
blockchain.stop()
|
||||||
|
|
||||||
# we use daemon threads, their termination is enforced.
|
# we use daemon threads, their termination is enforced.
|
||||||
# this sleep command gives them time to terminate cleanly.
|
# this sleep command gives them time to terminate cleanly.
|
||||||
|
@ -145,12 +149,12 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
|
|
||||||
# instanciate wallet for command-line
|
# instanciate wallet for command-line
|
||||||
wallet = Wallet(config)
|
wallet = Wallet(storage)
|
||||||
|
|
||||||
if cmd not in known_commands:
|
if cmd not in known_commands:
|
||||||
cmd = 'help'
|
cmd = 'help'
|
||||||
|
|
||||||
if not config.wallet_file_exists and cmd not in ['help','create','restore']:
|
if not storage.file_exists and cmd not in ['help','create','restore']:
|
||||||
print_msg("Error: Wallet file not found.")
|
print_msg("Error: Wallet file not found.")
|
||||||
print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
|
print_msg("Type 'electrum create' to create a new wallet, or provide a path to a wallet with the -w option")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
@ -197,13 +201,11 @@ if __name__ == '__main__':
|
||||||
if not interface.start(wait=True):
|
if not interface.start(wait=True):
|
||||||
print_msg("Not connected, aborting. Try option -o if you want to restore offline.")
|
print_msg("Not connected, aborting. Try option -o if you want to restore offline.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
wallet.interface = interface
|
|
||||||
verifier = WalletVerifier(interface, config)
|
|
||||||
verifier.start()
|
|
||||||
wallet.set_verifier(verifier)
|
|
||||||
|
|
||||||
|
blockchain = BlockchainVerifier(interface, config)
|
||||||
|
blockchain.start()
|
||||||
|
wallet.start_threads(interface, blockchain)
|
||||||
print_msg("Recovering wallet...")
|
print_msg("Recovering wallet...")
|
||||||
WalletSynchronizer(wallet, config).start()
|
|
||||||
wallet.update()
|
wallet.update()
|
||||||
if wallet.is_found():
|
if wallet.is_found():
|
||||||
print_msg("Recovery successful")
|
print_msg("Recovery successful")
|
||||||
|
@ -317,26 +319,21 @@ if __name__ == '__main__':
|
||||||
message = ' '.join(args[min_args:])
|
message = ' '.join(args[min_args:])
|
||||||
print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message))
|
print_msg("Warning: Final argument was reconstructed from several arguments:", repr(message))
|
||||||
args = args[0:min_args] + [ message ]
|
args = args[0:min_args] + [ message ]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# open session
|
# open session
|
||||||
if cmd not in offline_commands and not options.offline:
|
if cmd not in offline_commands and not options.offline:
|
||||||
interface = Interface(config)
|
interface = Interface(config)
|
||||||
interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
|
interface.register_callback('connected', lambda: sys.stderr.write("Connected to " + interface.connection_msg + "\n"))
|
||||||
|
|
||||||
if not interface.start(wait=True):
|
if not interface.start(wait=True):
|
||||||
print_msg("Not connected, aborting.")
|
print_msg("Not connected, aborting.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
wallet.interface = interface
|
blockchain = BlockchainVerifier(interface, config)
|
||||||
verifier = WalletVerifier(interface, config)
|
blockchain.start()
|
||||||
verifier.start()
|
wallet.start_threads(interface, blockchain)
|
||||||
wallet.set_verifier(verifier)
|
|
||||||
synchronizer = WalletSynchronizer(wallet, config)
|
|
||||||
synchronizer.start()
|
|
||||||
wallet.update()
|
wallet.update()
|
||||||
#wallet.save()
|
|
||||||
|
|
||||||
|
|
||||||
# run the command
|
# run the command
|
||||||
|
@ -350,9 +347,9 @@ if __name__ == '__main__':
|
||||||
if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
|
if raw_input("Are you sure you want to continue? (y/n) ") in ['y','Y','yes']:
|
||||||
wallet.config.path = ns
|
wallet.config.path = ns
|
||||||
wallet.seed = ''
|
wallet.seed = ''
|
||||||
wallet.config.set_key('seed', '', True)
|
wallet.storage.put('seed', '', True)
|
||||||
wallet.use_encryption = False
|
wallet.use_encryption = False
|
||||||
wallet.config.set_key('use_encryption', wallet.use_encryption, True)
|
wallet.storage.put('use_encryption', wallet.use_encryption, True)
|
||||||
for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
|
for k in wallet.imported_keys.keys(): wallet.imported_keys[k] = ''
|
||||||
wallet.config.set_key('imported_keys',wallet.imported_keys, True)
|
wallet.config.set_key('imported_keys',wallet.imported_keys, True)
|
||||||
print_msg("Done.")
|
print_msg("Done.")
|
||||||
|
@ -361,12 +358,12 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
elif cmd == 'getconfig':
|
elif cmd == 'getconfig':
|
||||||
key = args[1]
|
key = args[1]
|
||||||
print_msg(wallet.config.get(key))
|
print_msg(config.get(key))
|
||||||
|
|
||||||
elif cmd == 'setconfig':
|
elif cmd == 'setconfig':
|
||||||
key, value = args[1:3]
|
key, value = args[1:3]
|
||||||
if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
|
if key not in ['seed', 'seed_version', 'master_public_key', 'use_encryption']:
|
||||||
wallet.config.set_key(key, value, True)
|
config.set_key(key, value, True)
|
||||||
print_msg(True)
|
print_msg(True)
|
||||||
else:
|
else:
|
||||||
print_msg(False)
|
print_msg(False)
|
||||||
|
@ -394,8 +391,8 @@ if __name__ == '__main__':
|
||||||
|
|
||||||
|
|
||||||
if cmd not in offline_commands and not options.offline:
|
if cmd not in offline_commands and not options.offline:
|
||||||
verifier.stop()
|
wallet.stop_threads()
|
||||||
synchronizer.stop()
|
|
||||||
interface.stop()
|
interface.stop()
|
||||||
|
blockchain.stop()
|
||||||
time.sleep(0.1)
|
time.sleep(0.1)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
|
@ -42,7 +42,9 @@ except:
|
||||||
from electrum.wallet import format_satoshis
|
from electrum.wallet import format_satoshis
|
||||||
from electrum.bitcoin import Transaction, is_valid
|
from electrum.bitcoin import Transaction, is_valid
|
||||||
from electrum import mnemonic
|
from electrum import mnemonic
|
||||||
from electrum import util, bitcoin, commands, Interface, Wallet, WalletVerifier, WalletSynchronizer
|
from electrum import util, bitcoin, commands, Interface, Wallet
|
||||||
|
from electrum import SimpleConfig, Wallet, WalletStorage
|
||||||
|
|
||||||
|
|
||||||
import bmp, pyqrnative
|
import bmp, pyqrnative
|
||||||
import exchange_rate
|
import exchange_rate
|
||||||
|
@ -225,9 +227,12 @@ class ElectrumWindow(QMainWindow):
|
||||||
|
|
||||||
def __init__(self, config):
|
def __init__(self, config):
|
||||||
QMainWindow.__init__(self)
|
QMainWindow.__init__(self)
|
||||||
|
|
||||||
|
self.config = config
|
||||||
|
self.init_plugins()
|
||||||
|
|
||||||
self._close_electrum = False
|
self._close_electrum = False
|
||||||
self.lite = None
|
self.lite = None
|
||||||
self.config = config
|
|
||||||
self.current_account = self.config.get("current_account", None)
|
self.current_account = self.config.get("current_account", None)
|
||||||
|
|
||||||
self.icon = QIcon(os.getcwd() + '/icons/electrum.png')
|
self.icon = QIcon(os.getcwd() + '/icons/electrum.png')
|
||||||
|
@ -237,14 +242,13 @@ class ElectrumWindow(QMainWindow):
|
||||||
|
|
||||||
self.build_menu()
|
self.build_menu()
|
||||||
self.tray.show()
|
self.tray.show()
|
||||||
|
|
||||||
self.init_plugins()
|
|
||||||
self.create_status_bar()
|
self.create_status_bar()
|
||||||
|
|
||||||
self.need_update = threading.Event()
|
self.need_update = threading.Event()
|
||||||
|
|
||||||
self.expert_mode = config.get('classic_expert_mode', False)
|
self.expert_mode = config.get('classic_expert_mode', False)
|
||||||
self.decimal_point = config.get('decimal_point', 8)
|
self.decimal_point = config.get('decimal_point', 8)
|
||||||
|
self.num_zeros = int(config.get('num_zeros',0))
|
||||||
|
|
||||||
set_language(config.get('language'))
|
set_language(config.get('language'))
|
||||||
|
|
||||||
|
@ -287,11 +291,12 @@ class ElectrumWindow(QMainWindow):
|
||||||
tabs.setCurrentIndex (n)
|
tabs.setCurrentIndex (n)
|
||||||
tabs.setCurrentIndex (0)
|
tabs.setCurrentIndex (0)
|
||||||
|
|
||||||
|
|
||||||
# plugins that need to change the GUI do it here
|
# plugins that need to change the GUI do it here
|
||||||
self.run_hook('init')
|
self.run_hook('init')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def load_wallet(self, wallet):
|
def load_wallet(self, wallet):
|
||||||
import electrum
|
import electrum
|
||||||
self.wallet = wallet
|
self.wallet = wallet
|
||||||
|
@ -301,7 +306,7 @@ class ElectrumWindow(QMainWindow):
|
||||||
self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
|
self.wallet.interface.register_callback('disconnected', lambda: self.emit(QtCore.SIGNAL('update_status')))
|
||||||
self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
|
self.wallet.interface.register_callback('disconnecting', lambda: self.emit(QtCore.SIGNAL('update_status')))
|
||||||
self.wallet.interface.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
|
self.wallet.interface.register_callback('new_transaction', lambda: self.emit(QtCore.SIGNAL('transaction_signal')))
|
||||||
title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.config.path
|
title = 'Electrum ' + self.wallet.electrum_version + ' - ' + self.wallet.storage.path
|
||||||
if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
|
if not self.wallet.seed: title += ' [%s]' % (_('seedless'))
|
||||||
self.setWindowTitle( title )
|
self.setWindowTitle( title )
|
||||||
self.update_wallet()
|
self.update_wallet()
|
||||||
|
@ -312,59 +317,59 @@ class ElectrumWindow(QMainWindow):
|
||||||
|
|
||||||
# account selector
|
# account selector
|
||||||
accounts = self.wallet.get_accounts()
|
accounts = self.wallet.get_accounts()
|
||||||
|
self.account_selector.clear()
|
||||||
if len(accounts) > 1:
|
if len(accounts) > 1:
|
||||||
self.account_selector.addItems([_("All accounts")] + accounts.values())
|
self.account_selector.addItems([_("All accounts")] + accounts.values())
|
||||||
self.account_selector.setCurrentIndex(0)
|
self.account_selector.setCurrentIndex(0)
|
||||||
|
self.account_selector.show()
|
||||||
|
else:
|
||||||
|
self.account_selector.hide()
|
||||||
|
|
||||||
|
self.update_lock_icon()
|
||||||
|
self.update_buttons_on_seed()
|
||||||
|
self.update_console()
|
||||||
|
|
||||||
|
|
||||||
def select_wallet_file(self):
|
def select_wallet_file(self):
|
||||||
wallet_folder = self.wallet.config.path
|
wallet_folder = self.wallet.storage.path
|
||||||
re.sub("(\/\w*.dat)$", "", wallet_folder)
|
re.sub("(\/\w*.dat)$", "", wallet_folder)
|
||||||
file_name = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat") )
|
file_name = unicode( QFileDialog.getOpenFileName(self, "Select your wallet file", wallet_folder, "*.dat") )
|
||||||
return file_name
|
return file_name
|
||||||
|
|
||||||
|
|
||||||
def open_wallet(self):
|
def open_wallet(self):
|
||||||
from electrum import SimpleConfig, Wallet, WalletSynchronizer
|
|
||||||
|
|
||||||
filename = self.select_wallet_file()
|
filename = self.select_wallet_file()
|
||||||
if not filename:
|
if not filename:
|
||||||
return
|
return
|
||||||
|
|
||||||
config = SimpleConfig({'wallet_path': filename})
|
storage = WalletStorage({'wallet_path': filename})
|
||||||
if not config.wallet_file_exists:
|
if not storage.file_exists:
|
||||||
self.show_message("file not found "+ filename)
|
self.show_message("file not found "+ filename)
|
||||||
return
|
return
|
||||||
|
|
||||||
interface = self.wallet.interface
|
interface = self.wallet.interface
|
||||||
verifier = self.wallet.verifier
|
blockchain = self.wallet.verifier.blockchain
|
||||||
self.wallet.synchronizer.stop()
|
self.wallet.stop_threads()
|
||||||
|
|
||||||
self.config = config
|
# create new wallet
|
||||||
|
wallet = Wallet(storage)
|
||||||
# create wallet
|
wallet.start_threads(interface, blockchain)
|
||||||
wallet = Wallet(config)
|
|
||||||
wallet.interface = interface
|
|
||||||
wallet.verifier = verifier
|
|
||||||
synchronizer = WalletSynchronizer(wallet, config)
|
|
||||||
synchronizer.start()
|
|
||||||
|
|
||||||
self.load_wallet(wallet)
|
self.load_wallet(wallet)
|
||||||
|
|
||||||
|
|
||||||
def new_wallet(self):
|
def new_wallet(self):
|
||||||
from electrum import SimpleConfig, Wallet, WalletSynchronizer
|
|
||||||
import installwizard
|
import installwizard
|
||||||
|
|
||||||
wallet_folder = self.wallet.config.path
|
wallet_folder = self.wallet.storage.path
|
||||||
re.sub("(\/\w*.dat)$", "", wallet_folder)
|
re.sub("(\/\w*.dat)$", "", wallet_folder)
|
||||||
filename = self.getSaveFileName("Select your wallet file", wallet_folder, "*.dat")
|
filename = self.getSaveFileName("Select your wallet file", wallet_folder, "*.dat")
|
||||||
|
|
||||||
config = SimpleConfig({'wallet_path': filename})
|
storage = WalletStorage({'wallet_path': filename})
|
||||||
assert not config.wallet_file_exists
|
assert not storage.file_exists
|
||||||
|
|
||||||
wizard = installwizard.InstallWizard(config, self.wallet.interface)
|
wizard = installwizard.InstallWizard(self.config, self.wallet.interface, self.wallet.verifier.blockchain, storage)
|
||||||
wallet = wizard.run()
|
wallet = wizard.run()
|
||||||
if wallet:
|
if wallet:
|
||||||
self.load_wallet(wallet)
|
self.load_wallet(wallet)
|
||||||
|
@ -374,22 +379,29 @@ class ElectrumWindow(QMainWindow):
|
||||||
def init_menubar(self):
|
def init_menubar(self):
|
||||||
menubar = QMenuBar()
|
menubar = QMenuBar()
|
||||||
|
|
||||||
electrum_menu = menubar.addMenu(_("&File"))
|
file_menu = menubar.addMenu(_("&File"))
|
||||||
open_wallet_action = electrum_menu.addAction(_("Open wallet"))
|
open_wallet_action = file_menu.addAction(_("&Open"))
|
||||||
open_wallet_action.triggered.connect(self.open_wallet)
|
open_wallet_action.triggered.connect(self.open_wallet)
|
||||||
|
|
||||||
new_wallet_action = electrum_menu.addAction(_("New wallet"))
|
new_wallet_action = file_menu.addAction(_("&Create/Restore"))
|
||||||
new_wallet_action.triggered.connect(self.new_wallet)
|
new_wallet_action.triggered.connect(self.new_wallet)
|
||||||
|
|
||||||
preferences_name = _("Preferences")
|
wallet_backup = file_menu.addAction(_("&Copy"))
|
||||||
if sys.platform == 'darwin':
|
wallet_backup.triggered.connect(lambda: backup_wallet(self.wallet.storage.path))
|
||||||
preferences_name = _("Electrum preferences") # Settings / Preferences are all reserved keywords in OSX using this as work around
|
|
||||||
|
|
||||||
preferences_menu = electrum_menu.addAction(preferences_name)
|
quit_item = file_menu.addAction(_("&Close"))
|
||||||
|
quit_item.triggered.connect(self.close)
|
||||||
|
|
||||||
|
wallet_menu = menubar.addMenu(_("&Wallet"))
|
||||||
|
|
||||||
|
# Settings / Preferences are all reserved keywords in OSX using this as work around
|
||||||
|
preferences_name = _("Electrum preferences") if sys.platform == 'darwin' else _("Preferences")
|
||||||
|
preferences_menu = wallet_menu.addAction(preferences_name)
|
||||||
preferences_menu.triggered.connect(self.settings_dialog)
|
preferences_menu.triggered.connect(self.settings_dialog)
|
||||||
electrum_menu.addSeparator()
|
|
||||||
|
|
||||||
raw_transaction_menu = electrum_menu.addMenu(_("&Load raw transaction"))
|
wallet_menu.addSeparator()
|
||||||
|
|
||||||
|
raw_transaction_menu = wallet_menu.addMenu(_("&Load raw transaction"))
|
||||||
|
|
||||||
raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
|
raw_transaction_file = raw_transaction_menu.addAction(_("&From file"))
|
||||||
raw_transaction_file.triggered.connect(self.do_process_from_file)
|
raw_transaction_file.triggered.connect(self.do_process_from_file)
|
||||||
|
@ -397,13 +409,7 @@ class ElectrumWindow(QMainWindow):
|
||||||
raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
|
raw_transaction_text = raw_transaction_menu.addAction(_("&From text"))
|
||||||
raw_transaction_text.triggered.connect(self.do_process_from_text)
|
raw_transaction_text.triggered.connect(self.do_process_from_text)
|
||||||
|
|
||||||
electrum_menu.addSeparator()
|
wallet_menu.addSeparator()
|
||||||
quit_item = electrum_menu.addAction(_("&Close"))
|
|
||||||
quit_item.triggered.connect(self.close)
|
|
||||||
|
|
||||||
wallet_menu = menubar.addMenu(_("&Wallet"))
|
|
||||||
wallet_backup = wallet_menu.addAction(_("&Create backup"))
|
|
||||||
wallet_backup.triggered.connect(lambda: backup_wallet(self.config.path))
|
|
||||||
|
|
||||||
show_menu = wallet_menu.addMenu(_("Show"))
|
show_menu = wallet_menu.addMenu(_("Show"))
|
||||||
|
|
||||||
|
@ -514,14 +520,15 @@ class ElectrumWindow(QMainWindow):
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def set_label(self, name, text = None):
|
def set_label(self, name, text = None):
|
||||||
changed = False
|
changed = False
|
||||||
old_text = self.wallet.labels.get(name)
|
old_text = self.wallet.labels.get(name)
|
||||||
if text:
|
if text:
|
||||||
if old_text != text:
|
if old_text != text:
|
||||||
self.wallet.labels[name] = text
|
self.wallet.labels[name] = text
|
||||||
self.wallet.config.set_key('labels', self.wallet.labels)
|
self.wallet.storage.put('labels', self.wallet.labels)
|
||||||
changed = True
|
changed = True
|
||||||
else:
|
else:
|
||||||
if old_text:
|
if old_text:
|
||||||
|
@ -564,7 +571,7 @@ class ElectrumWindow(QMainWindow):
|
||||||
self.run_hook('timer_actions')
|
self.run_hook('timer_actions')
|
||||||
|
|
||||||
def format_amount(self, x, is_diff=False, whitespaces=False):
|
def format_amount(self, x, is_diff=False, whitespaces=False):
|
||||||
return format_satoshis(x, is_diff, self.wallet.num_zeros, self.decimal_point, whitespaces)
|
return format_satoshis(x, is_diff, self.num_zeros, self.decimal_point, whitespaces)
|
||||||
|
|
||||||
def read_amount(self, x):
|
def read_amount(self, x):
|
||||||
if x in['.', '']: return None
|
if x in['.', '']: return None
|
||||||
|
@ -850,14 +857,10 @@ class ElectrumWindow(QMainWindow):
|
||||||
_('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
|
_('Bitcoin transactions are in general not free. A transaction fee is paid by the sender of the funds.') + '\n\n'\
|
||||||
+ _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
|
+ _('The amount of fee can be decided freely by the sender. However, transactions with low fees take more time to be processed.') + '\n\n'\
|
||||||
+ _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 4, 3)
|
+ _('A suggested fee is automatically added to this field. You may override it. The suggested fee increases with the size of the transaction.')), 4, 3)
|
||||||
b = ''
|
|
||||||
|
|
||||||
if 1:#self.wallet.seed:
|
|
||||||
b = EnterButton(_("Send"), self.do_send)
|
|
||||||
else:
|
|
||||||
b = EnterButton(_("Create unsigned transaction"), self.do_send)
|
|
||||||
|
|
||||||
grid.addWidget(b, 6, 1)
|
self.send_button = EnterButton(_("Send"), self.do_send)
|
||||||
|
grid.addWidget(self.send_button, 6, 1)
|
||||||
|
|
||||||
b = EnterButton(_("Clear"),self.do_clear)
|
b = EnterButton(_("Clear"),self.do_clear)
|
||||||
grid.addWidget(b, 6, 2)
|
grid.addWidget(b, 6, 2)
|
||||||
|
@ -1339,10 +1342,12 @@ class ElectrumWindow(QMainWindow):
|
||||||
from qt_console import Console
|
from qt_console import Console
|
||||||
self.console = console = Console()
|
self.console = console = Console()
|
||||||
return console
|
return console
|
||||||
#
|
|
||||||
|
|
||||||
self.console.history = self.config.get("console-history",[])
|
|
||||||
self.console.history_index = len(self.console.history)
|
def update_console(self):
|
||||||
|
console = self.console
|
||||||
|
console.history = self.config.get("console-history",[])
|
||||||
|
console.history_index = len(console.history)
|
||||||
|
|
||||||
console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
|
console.updateNamespace({'wallet' : self.wallet, 'interface' : self.wallet.interface, 'gui':self})
|
||||||
console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
|
console.updateNamespace({'util' : util, 'bitcoin':bitcoin})
|
||||||
|
@ -1356,7 +1361,7 @@ class ElectrumWindow(QMainWindow):
|
||||||
methods[m] = mkfunc(c._run, m)
|
methods[m] = mkfunc(c._run, m)
|
||||||
|
|
||||||
console.updateNamespace(methods)
|
console.updateNamespace(methods)
|
||||||
return console
|
|
||||||
|
|
||||||
def change_account(self,s):
|
def change_account(self,s):
|
||||||
if s == _("All accounts"):
|
if s == _("All accounts"):
|
||||||
|
@ -1389,13 +1394,14 @@ class ElectrumWindow(QMainWindow):
|
||||||
|
|
||||||
if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
|
if (int(qtVersion[0]) >= 4 and int(qtVersion[2]) >= 7):
|
||||||
sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
|
sb.addPermanentWidget( StatusBarButton( QIcon(":icons/switchgui.png"), _("Switch to Lite Mode"), self.go_lite ) )
|
||||||
if 1:#self.wallet.seed:
|
|
||||||
self.lock_icon = QIcon(":icons/lock.png") #if self.wallet.use_encryption else QIcon(":icons/unlock.png")
|
self.lock_icon = QIcon()
|
||||||
self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
|
self.password_button = StatusBarButton( self.lock_icon, _("Password"), self.change_password_dialog )
|
||||||
sb.addPermanentWidget( self.password_button )
|
sb.addPermanentWidget( self.password_button )
|
||||||
|
|
||||||
sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
|
sb.addPermanentWidget( StatusBarButton( QIcon(":icons/preferences.png"), _("Preferences"), self.settings_dialog ) )
|
||||||
if 1:#self.wallet.seed:
|
self.seed_button = StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog )
|
||||||
sb.addPermanentWidget( StatusBarButton( QIcon(":icons/seed.png"), _("Seed"), self.show_seed_dialog ) )
|
sb.addPermanentWidget( self.seed_button )
|
||||||
self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
|
self.status_button = StatusBarButton( QIcon(":icons/status_disconnected.png"), _("Network"), self.run_network_dialog )
|
||||||
sb.addPermanentWidget( self.status_button )
|
sb.addPermanentWidget( self.status_button )
|
||||||
|
|
||||||
|
@ -1404,10 +1410,27 @@ class ElectrumWindow(QMainWindow):
|
||||||
self.setStatusBar(sb)
|
self.setStatusBar(sb)
|
||||||
|
|
||||||
|
|
||||||
|
def update_lock_icon(self):
|
||||||
|
icon = QIcon(":icons/lock.png") if self.wallet.use_encryption else QIcon(":icons/unlock.png")
|
||||||
|
self.password_button.setIcon( icon )
|
||||||
|
|
||||||
|
|
||||||
|
def update_buttons_on_seed(self):
|
||||||
|
if self.wallet.seed:
|
||||||
|
self.seed_button.show()
|
||||||
|
self.password_button.show()
|
||||||
|
self.send_button.setText(_("Send"))
|
||||||
|
else:
|
||||||
|
self.password_button.hide()
|
||||||
|
self.seed_button.hide()
|
||||||
|
self.send_button.setText(_("Create unsigned transaction"))
|
||||||
|
|
||||||
|
|
||||||
def change_password_dialog(self):
|
def change_password_dialog(self):
|
||||||
from password_dialog import PasswordDialog
|
from password_dialog import PasswordDialog
|
||||||
d = PasswordDialog(self.wallet, self)
|
d = PasswordDialog(self.wallet, self)
|
||||||
d.run()
|
d.run()
|
||||||
|
self.update_lock_icon()
|
||||||
|
|
||||||
|
|
||||||
def go_lite(self):
|
def go_lite(self):
|
||||||
|
@ -1420,6 +1443,7 @@ class ElectrumWindow(QMainWindow):
|
||||||
self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
|
self.lite = gui_lite.ElectrumGui(self.wallet, self.config, self)
|
||||||
self.lite.main(None)
|
self.lite.main(None)
|
||||||
|
|
||||||
|
|
||||||
def new_contact_dialog(self):
|
def new_contact_dialog(self):
|
||||||
text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
|
text, ok = QInputDialog.getText(self, _('New Contact'), _('Address') + ':')
|
||||||
address = unicode(text)
|
address = unicode(text)
|
||||||
|
@ -1441,7 +1465,7 @@ class ElectrumWindow(QMainWindow):
|
||||||
|
|
||||||
addr = self.wallet.new_account_address()
|
addr = self.wallet.new_account_address()
|
||||||
vbox = QVBoxLayout()
|
vbox = QVBoxLayout()
|
||||||
vbox.addWidget(QLabel("To add another account, please send bitcoins to the following address:"))
|
vbox.addWidget(QLabel(_("To create a new account, please send coins to the first address of that account:")))
|
||||||
e = QLineEdit(addr)
|
e = QLineEdit(addr)
|
||||||
e.setReadOnly(True)
|
e.setReadOnly(True)
|
||||||
vbox.addWidget(e)
|
vbox.addWidget(e)
|
||||||
|
@ -1941,7 +1965,7 @@ class ElectrumWindow(QMainWindow):
|
||||||
nz_label = QLabel(_('Display zeros'))
|
nz_label = QLabel(_('Display zeros'))
|
||||||
grid_ui.addWidget(nz_label, 0, 0)
|
grid_ui.addWidget(nz_label, 0, 0)
|
||||||
nz_e = AmountEdit(None,True)
|
nz_e = AmountEdit(None,True)
|
||||||
nz_e.setText("%d"% self.wallet.num_zeros)
|
nz_e.setText("%d"% self.num_zeros)
|
||||||
grid_ui.addWidget(nz_e, 0, 1)
|
grid_ui.addWidget(nz_e, 0, 1)
|
||||||
msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
|
msg = _('Number of zeros displayed after the decimal point. For example, if this is set to 2, "1." will be displayed as "1.00"')
|
||||||
grid_ui.addWidget(HelpButton(msg), 0, 2)
|
grid_ui.addWidget(HelpButton(msg), 0, 2)
|
||||||
|
@ -2097,8 +2121,8 @@ class ElectrumWindow(QMainWindow):
|
||||||
QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
|
QMessageBox.warning(self, _('Error'), _('Invalid value')+':%s'%nz, _('OK'))
|
||||||
return
|
return
|
||||||
|
|
||||||
if self.wallet.num_zeros != nz:
|
if self.num_zeros != nz:
|
||||||
self.wallet.num_zeros = nz
|
self.num_zeros = nz
|
||||||
self.config.set_key('num_zeros', nz, True)
|
self.config.set_key('num_zeros', nz, True)
|
||||||
self.update_history_tab()
|
self.update_history_tab()
|
||||||
self.update_receive_tab()
|
self.update_receive_tab()
|
||||||
|
@ -2155,7 +2179,7 @@ class ElectrumWindow(QMainWindow):
|
||||||
g = self.geometry()
|
g = self.geometry()
|
||||||
self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
|
self.config.set_key("winpos-qt", [g.left(),g.top(),g.width(),g.height()], True)
|
||||||
self.save_column_widths()
|
self.save_column_widths()
|
||||||
self.config.set_key("console-history",self.console.history[-50:])
|
self.config.set_key("console-history", self.console.history[-50:], True)
|
||||||
event.accept()
|
event.accept()
|
||||||
|
|
||||||
class OpenFileEventFilter(QObject):
|
class OpenFileEventFilter(QObject):
|
||||||
|
@ -2175,9 +2199,10 @@ class OpenFileEventFilter(QObject):
|
||||||
|
|
||||||
class ElectrumGui:
|
class ElectrumGui:
|
||||||
|
|
||||||
def __init__(self, config, interface, app=None):
|
def __init__(self, config, interface, blockchain, app=None):
|
||||||
self.interface = interface
|
self.interface = interface
|
||||||
self.config = config
|
self.config = config
|
||||||
|
self.blockchain = blockchain
|
||||||
self.windows = []
|
self.windows = []
|
||||||
self.efilter = OpenFileEventFilter(self.windows)
|
self.efilter = OpenFileEventFilter(self.windows)
|
||||||
if app is None:
|
if app is None:
|
||||||
|
@ -2186,24 +2211,18 @@ class ElectrumGui:
|
||||||
|
|
||||||
|
|
||||||
def main(self, url):
|
def main(self, url):
|
||||||
|
|
||||||
found = self.config.wallet_file_exists
|
storage = WalletStorage(self.config)
|
||||||
if not found:
|
if not storage.file_exists:
|
||||||
import installwizard
|
import installwizard
|
||||||
wizard = installwizard.InstallWizard(self.config, self.interface)
|
wizard = installwizard.InstallWizard(self.config, self.interface, self.blockchain, storage)
|
||||||
wallet = wizard.run()
|
wallet = wizard.run()
|
||||||
if not wallet:
|
if not wallet:
|
||||||
exit()
|
exit()
|
||||||
else:
|
else:
|
||||||
wallet = Wallet(self.config)
|
wallet = Wallet(storage)
|
||||||
|
|
||||||
wallet.interface = self.interface
|
wallet.start_threads(self.interface, self.blockchain)
|
||||||
|
|
||||||
verifier = WalletVerifier(self.interface, self.config)
|
|
||||||
verifier.start()
|
|
||||||
wallet.set_verifier(verifier)
|
|
||||||
synchronizer = WalletSynchronizer(wallet, self.config)
|
|
||||||
synchronizer.start()
|
|
||||||
|
|
||||||
s = Timer()
|
s = Timer()
|
||||||
s.start()
|
s.start()
|
||||||
|
@ -2219,7 +2238,6 @@ class ElectrumGui:
|
||||||
|
|
||||||
self.app.exec_()
|
self.app.exec_()
|
||||||
|
|
||||||
verifier.stop()
|
wallet.stop_threads()
|
||||||
synchronizer.stop()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,24 @@ _ = lambda x:x
|
||||||
from electrum.util import format_satoshis, set_verbosity
|
from electrum.util import format_satoshis, set_verbosity
|
||||||
from electrum.bitcoin import is_valid
|
from electrum.bitcoin import is_valid
|
||||||
|
|
||||||
|
from electrum import Wallet, WalletStorage
|
||||||
|
|
||||||
import tty, sys
|
import tty, sys
|
||||||
|
|
||||||
|
|
||||||
class ElectrumGui:
|
class ElectrumGui:
|
||||||
|
|
||||||
def __init__(self, wallet, config, app=None):
|
def __init__(self, config, interface, blockchain):
|
||||||
|
|
||||||
|
self.config = config
|
||||||
|
storage = WalletStorage(config)
|
||||||
|
if not storage.file_exists:
|
||||||
|
print "Wallet not found. try 'electrum create'"
|
||||||
|
exit()
|
||||||
|
|
||||||
|
self.wallet = Wallet(storage)
|
||||||
|
self.wallet.start_threads(interface, blockchain)
|
||||||
|
|
||||||
self.stdscr = curses.initscr()
|
self.stdscr = curses.initscr()
|
||||||
curses.noecho()
|
curses.noecho()
|
||||||
curses.cbreak()
|
curses.cbreak()
|
||||||
|
@ -24,8 +36,6 @@ class ElectrumGui:
|
||||||
self.set_cursor(0)
|
self.set_cursor(0)
|
||||||
self.w = curses.newwin(10, 50, 5, 5)
|
self.w = curses.newwin(10, 50, 5, 5)
|
||||||
|
|
||||||
self.wallet = wallet
|
|
||||||
self.config = config
|
|
||||||
set_verbosity(False)
|
set_verbosity(False)
|
||||||
self.tab = 0
|
self.tab = 0
|
||||||
self.pos = 0
|
self.pos = 0
|
||||||
|
|
|
@ -3,7 +3,7 @@ from PyQt4.QtCore import *
|
||||||
import PyQt4.QtCore as QtCore
|
import PyQt4.QtCore as QtCore
|
||||||
from i18n import _
|
from i18n import _
|
||||||
|
|
||||||
from electrum import Wallet, mnemonic, WalletVerifier, WalletSynchronizer
|
from electrum import Wallet, mnemonic
|
||||||
|
|
||||||
from seed_dialog import SeedDialog
|
from seed_dialog import SeedDialog
|
||||||
from network_dialog import NetworkDialog
|
from network_dialog import NetworkDialog
|
||||||
|
@ -14,10 +14,12 @@ import sys
|
||||||
|
|
||||||
class InstallWizard(QDialog):
|
class InstallWizard(QDialog):
|
||||||
|
|
||||||
def __init__(self, config, interface):
|
def __init__(self, config, interface, blockchain, storage):
|
||||||
QDialog.__init__(self)
|
QDialog.__init__(self)
|
||||||
self.config = config
|
self.config = config
|
||||||
self.interface = interface
|
self.interface = interface
|
||||||
|
self.blockchain = blockchain
|
||||||
|
self.storage = storage
|
||||||
|
|
||||||
|
|
||||||
def restore_or_create(self):
|
def restore_or_create(self):
|
||||||
|
@ -124,6 +126,15 @@ class InstallWizard(QDialog):
|
||||||
wallet.set_up_to_date(False)
|
wallet.set_up_to_date(False)
|
||||||
wallet.interface.poke('synchronizer')
|
wallet.interface.poke('synchronizer')
|
||||||
waiting_dialog(waiting)
|
waiting_dialog(waiting)
|
||||||
|
|
||||||
|
# try to restore old account
|
||||||
|
if not wallet.is_found():
|
||||||
|
print "trying old method"
|
||||||
|
wallet.create_old_account()
|
||||||
|
wallet.set_up_to_date(False)
|
||||||
|
wallet.interface.poke('synchronizer')
|
||||||
|
waiting_dialog(waiting)
|
||||||
|
|
||||||
if wallet.is_found():
|
if wallet.is_found():
|
||||||
QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK'))
|
QMessageBox.information(None, _('Information'), _("Recovery successful"), _('OK'))
|
||||||
else:
|
else:
|
||||||
|
@ -137,8 +148,7 @@ class InstallWizard(QDialog):
|
||||||
a = self.restore_or_create()
|
a = self.restore_or_create()
|
||||||
if not a: exit()
|
if not a: exit()
|
||||||
|
|
||||||
wallet = Wallet(self.config)
|
wallet = Wallet(self.storage)
|
||||||
wallet.interface = self.interface
|
|
||||||
|
|
||||||
if a =='create':
|
if a =='create':
|
||||||
wallet.init_seed(None)
|
wallet.init_seed(None)
|
||||||
|
@ -170,11 +180,7 @@ class InstallWizard(QDialog):
|
||||||
#self.interface.start(wait = False)
|
#self.interface.start(wait = False)
|
||||||
|
|
||||||
# start wallet threads
|
# start wallet threads
|
||||||
verifier = WalletVerifier(self.interface, self.config)
|
wallet.start_threads(self.interface, self.blockchain)
|
||||||
verifier.start()
|
|
||||||
wallet.set_verifier(verifier)
|
|
||||||
synchronizer = WalletSynchronizer(wallet, self.config)
|
|
||||||
synchronizer.start()
|
|
||||||
|
|
||||||
|
|
||||||
# generate the first addresses, in case we are offline
|
# generate the first addresses, in case we are offline
|
||||||
|
|
|
@ -44,7 +44,7 @@ class NetworkDialog(QDialog):
|
||||||
|
|
||||||
if parent:
|
if parent:
|
||||||
if interface.is_connected:
|
if interface.is_connected:
|
||||||
status = _("Connected to")+" %s"%(interface.host) + "\n%d "%(parent.wallet.verifier.height)+_("blocks")
|
status = _("Connected to")+" %s"%(interface.host) + "\n%d "%(parent.wallet.verifier.blockchain.height)+_("blocks")
|
||||||
else:
|
else:
|
||||||
status = _("Not connected")
|
status = _("Not connected")
|
||||||
server = interface.server
|
server = interface.server
|
||||||
|
|
|
@ -96,9 +96,5 @@ class PasswordDialog(QDialog):
|
||||||
|
|
||||||
QMessageBox.information(self.parent, _('Success'), _('Password was updated successfully'), _('OK'))
|
QMessageBox.information(self.parent, _('Success'), _('Password was updated successfully'), _('OK'))
|
||||||
|
|
||||||
if self.parent:
|
|
||||||
icon = QIcon(":icons/lock.png") if wallet.use_encryption else QIcon(":icons/unlock.png")
|
|
||||||
self.parent.password_button.setIcon( icon )
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
from version import ELECTRUM_VERSION
|
from version import ELECTRUM_VERSION
|
||||||
from util import format_satoshis, print_msg, print_json, print_error, set_verbosity
|
from util import format_satoshis, print_msg, print_json, print_error, set_verbosity
|
||||||
from wallet import WalletSynchronizer
|
from wallet import WalletSynchronizer, WalletStorage
|
||||||
from wallet_factory import WalletFactory as Wallet
|
from wallet_factory import WalletFactory as Wallet
|
||||||
from verifier import WalletVerifier
|
from verifier import TxVerifier
|
||||||
|
from blockchain import BlockchainVerifier
|
||||||
from interface import Interface, pick_random_server, DEFAULT_SERVERS
|
from interface import Interface, pick_random_server, DEFAULT_SERVERS
|
||||||
from simple_config import SimpleConfig
|
from simple_config import SimpleConfig
|
||||||
import bitcoin
|
import bitcoin
|
||||||
|
|
|
@ -48,10 +48,13 @@ class Account(object):
|
||||||
class OldAccount(Account):
|
class OldAccount(Account):
|
||||||
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """
|
""" Privatekey(type,n) = Master_private_key + H(n|S|type) """
|
||||||
|
|
||||||
def __init__(self, mpk, mpk2 = None, mpk3 = None):
|
def __init__(self, v):
|
||||||
self.mpk = mpk
|
self.addresses = v.get(0, [])
|
||||||
self.mpk2 = mpk2
|
self.change = v.get(1, [])
|
||||||
self.mpk3 = mpk3
|
self.mpk = v['mpk'].decode('hex')
|
||||||
|
|
||||||
|
def dump(self):
|
||||||
|
return {0:self.addresses, 1:self.change}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def mpk_from_seed(klass, seed):
|
def mpk_from_seed(klass, seed):
|
||||||
|
@ -68,48 +71,34 @@ class OldAccount(Account):
|
||||||
seed = hashlib.sha256(seed + oldseed).digest()
|
seed = hashlib.sha256(seed + oldseed).digest()
|
||||||
return string_to_number( seed )
|
return string_to_number( seed )
|
||||||
|
|
||||||
def get_sequence(self, sequence, mpk):
|
def get_sequence(self, for_change, n):
|
||||||
for_change, n = sequence
|
return string_to_number( Hash( "%d:%d:"%(n,for_change) + self.mpk ) )
|
||||||
return string_to_number( Hash( "%d:%d:"%(n,for_change) + mpk.decode('hex') ) )
|
|
||||||
|
|
||||||
def get_address(self, sequence):
|
def get_address(self, for_change, n):
|
||||||
if not self.mpk2:
|
pubkey = self.get_pubkey(for_change, n)
|
||||||
pubkey = self.get_pubkey(sequence)
|
address = public_key_to_bc_address( pubkey.decode('hex') )
|
||||||
address = public_key_to_bc_address( pubkey.decode('hex') )
|
|
||||||
elif not self.mpk3:
|
|
||||||
pubkey1 = self.get_pubkey(sequence)
|
|
||||||
pubkey2 = self.get_pubkey(sequence, mpk = self.mpk2)
|
|
||||||
address = Transaction.multisig_script([pubkey1, pubkey2], 2)["address"]
|
|
||||||
else:
|
|
||||||
pubkey1 = self.get_pubkey(sequence)
|
|
||||||
pubkey2 = self.get_pubkey(sequence, mpk = self.mpk2)
|
|
||||||
pubkey3 = self.get_pubkey(sequence, mpk = self.mpk3)
|
|
||||||
address = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)["address"]
|
|
||||||
return address
|
return address
|
||||||
|
|
||||||
def get_pubkey(self, sequence, mpk=None):
|
def get_pubkey(self, for_change, n):
|
||||||
curve = SECP256k1
|
curve = SECP256k1
|
||||||
if mpk is None: mpk = self.mpk
|
mpk = self.mpk
|
||||||
z = self.get_sequence(sequence, mpk)
|
z = self.get_sequence(for_change, n)
|
||||||
master_public_key = ecdsa.VerifyingKey.from_string( mpk.decode('hex'), curve = SECP256k1 )
|
master_public_key = ecdsa.VerifyingKey.from_string( mpk, curve = SECP256k1 )
|
||||||
pubkey_point = master_public_key.pubkey.point + z*curve.generator
|
pubkey_point = master_public_key.pubkey.point + z*curve.generator
|
||||||
public_key2 = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 )
|
public_key2 = ecdsa.VerifyingKey.from_public_point( pubkey_point, curve = SECP256k1 )
|
||||||
return '04' + public_key2.to_string().encode('hex')
|
return '04' + public_key2.to_string().encode('hex')
|
||||||
|
|
||||||
def get_private_key_from_stretched_exponent(self, sequence, secexp):
|
def get_private_key_from_stretched_exponent(self, for_change, n, secexp):
|
||||||
order = generator_secp256k1.order()
|
order = generator_secp256k1.order()
|
||||||
secexp = ( secexp + self.get_sequence(sequence, self.mpk) ) % order
|
secexp = ( secexp + self.get_sequence(for_change, n) ) % order
|
||||||
pk = number_to_string( secexp, generator_secp256k1.order() )
|
pk = number_to_string( secexp, generator_secp256k1.order() )
|
||||||
compressed = False
|
compressed = False
|
||||||
return SecretToASecret( pk, compressed )
|
return SecretToASecret( pk, compressed )
|
||||||
|
|
||||||
def get_private_key(self, sequence, seed):
|
def get_private_key(self, seed, sequence):
|
||||||
|
for_change, n = sequence
|
||||||
secexp = self.stretch_key(seed)
|
secexp = self.stretch_key(seed)
|
||||||
return self.get_private_key_from_stretched_exponent(sequence, secexp)
|
return self.get_private_key_from_stretched_exponent(for_change, n, secexp)
|
||||||
|
|
||||||
def get_private_keys(self, sequence_list, seed):
|
|
||||||
secexp = self.stretch_key(seed)
|
|
||||||
return [ self.get_private_key_from_stretched_exponent( sequence, secexp) for sequence in sequence_list]
|
|
||||||
|
|
||||||
def check_seed(self, seed):
|
def check_seed(self, seed):
|
||||||
curve = SECP256k1
|
curve = SECP256k1
|
||||||
|
@ -121,23 +110,8 @@ class OldAccount(Account):
|
||||||
raise BaseException('Invalid password')
|
raise BaseException('Invalid password')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def get_input_info(self, sequence):
|
def redeem_script(self, sequence):
|
||||||
if not self.mpk2:
|
return None
|
||||||
pk_addr = self.get_address(sequence)
|
|
||||||
redeemScript = None
|
|
||||||
elif not self.mpk3:
|
|
||||||
pubkey1 = self.get_pubkey(sequence)
|
|
||||||
pubkey2 = self.get_pubkey(sequence,mpk=self.mpk2)
|
|
||||||
pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key
|
|
||||||
redeemScript = Transaction.multisig_script([pubkey1, pubkey2], 2)['redeemScript']
|
|
||||||
else:
|
|
||||||
pubkey1 = self.get_pubkey(sequence)
|
|
||||||
pubkey2 = self.get_pubkey(sequence, mpk=self.mpk2)
|
|
||||||
pubkey3 = self.get_pubkey(sequence, mpk=self.mpk3)
|
|
||||||
pk_addr = public_key_to_bc_address( pubkey1.decode('hex') ) # we need to return that address to get the right private key
|
|
||||||
redeemScript = Transaction.multisig_script([pubkey1, pubkey2, pubkey3], 2)['redeemScript']
|
|
||||||
return pk_addr, redeemScript
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class BIP32_Account(Account):
|
class BIP32_Account(Account):
|
||||||
|
|
|
@ -0,0 +1,329 @@
|
||||||
|
#!/usr/bin/env python
|
||||||
|
#
|
||||||
|
# Electrum - lightweight Bitcoin client
|
||||||
|
# Copyright (C) 2012 thomasv@ecdsa.org
|
||||||
|
#
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
#
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU General Public License for more details.
|
||||||
|
#
|
||||||
|
# You should have received a copy of the GNU General Public License
|
||||||
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
|
||||||
|
import threading, time, Queue, os, sys, shutil
|
||||||
|
from util import user_dir, appdata_dir, print_error
|
||||||
|
from bitcoin import *
|
||||||
|
|
||||||
|
|
||||||
|
class BlockchainVerifier(threading.Thread):
|
||||||
|
""" Simple Payment Verification """
|
||||||
|
|
||||||
|
def __init__(self, interface, config):
|
||||||
|
threading.Thread.__init__(self)
|
||||||
|
self.daemon = True
|
||||||
|
self.config = config
|
||||||
|
self.interface = interface
|
||||||
|
self.interface.register_channel('verifier')
|
||||||
|
self.lock = threading.Lock()
|
||||||
|
self.pending_headers = [] # headers that have not been verified
|
||||||
|
self.height = 0
|
||||||
|
self.local_height = 0
|
||||||
|
self.running = False
|
||||||
|
self.headers_url = 'http://headers.electrum.org/blockchain_headers'
|
||||||
|
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
with self.lock: self.running = False
|
||||||
|
self.interface.poke('verifier')
|
||||||
|
|
||||||
|
def is_running(self):
|
||||||
|
with self.lock: return self.running
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
|
||||||
|
self.init_headers_file()
|
||||||
|
self.set_local_height()
|
||||||
|
|
||||||
|
with self.lock:
|
||||||
|
self.running = True
|
||||||
|
requested_chunks = []
|
||||||
|
requested_headers = []
|
||||||
|
all_chunks = False
|
||||||
|
|
||||||
|
# subscribe to block headers
|
||||||
|
self.interface.send([ ('blockchain.headers.subscribe',[])], 'verifier')
|
||||||
|
|
||||||
|
while self.is_running():
|
||||||
|
# request missing chunks
|
||||||
|
if not all_chunks and self.height and not requested_chunks:
|
||||||
|
|
||||||
|
if self.local_height + 50 < self.height:
|
||||||
|
min_index = (self.local_height + 1)/2016
|
||||||
|
max_index = (self.height + 1)/2016
|
||||||
|
for i in range(min_index, max_index + 1):
|
||||||
|
print_error( "requesting chunk", i )
|
||||||
|
self.interface.send([ ('blockchain.block.get_chunk',[i])], 'verifier')
|
||||||
|
requested_chunks.append(i)
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
all_chunks = True
|
||||||
|
print_error("downloaded all chunks")
|
||||||
|
|
||||||
|
|
||||||
|
# process pending headers
|
||||||
|
if self.pending_headers and all_chunks:
|
||||||
|
done = []
|
||||||
|
for header in self.pending_headers:
|
||||||
|
if self.verify_header(header):
|
||||||
|
done.append(header)
|
||||||
|
else:
|
||||||
|
# request previous header
|
||||||
|
i = header.get('block_height') - 1
|
||||||
|
if i not in requested_headers:
|
||||||
|
print_error("requesting header %d"%i)
|
||||||
|
self.interface.send([ ('blockchain.block.get_header',[i])], 'verifier')
|
||||||
|
requested_headers.append(i)
|
||||||
|
# no point continuing
|
||||||
|
break
|
||||||
|
if done:
|
||||||
|
self.interface.trigger_callback('updated')
|
||||||
|
for header in done:
|
||||||
|
self.pending_headers.remove(header)
|
||||||
|
|
||||||
|
try:
|
||||||
|
r = self.interface.get_response('verifier',timeout=1)
|
||||||
|
except Queue.Empty:
|
||||||
|
continue
|
||||||
|
if not r: continue
|
||||||
|
|
||||||
|
if r.get('error'):
|
||||||
|
print_error('Verifier received an error:', r)
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 3. handle response
|
||||||
|
method = r['method']
|
||||||
|
params = r['params']
|
||||||
|
result = r['result']
|
||||||
|
|
||||||
|
if method == 'blockchain.block.get_chunk':
|
||||||
|
index = params[0]
|
||||||
|
self.verify_chunk(index, result)
|
||||||
|
requested_chunks.remove(index)
|
||||||
|
|
||||||
|
elif method in ['blockchain.headers.subscribe', 'blockchain.block.get_header']:
|
||||||
|
|
||||||
|
self.pending_headers.append(result)
|
||||||
|
if method == 'blockchain.block.get_header':
|
||||||
|
requested_headers.remove(result.get('block_height'))
|
||||||
|
else:
|
||||||
|
self.height = result.get('block_height')
|
||||||
|
## fixme # self.interface.poke('synchronizer')
|
||||||
|
|
||||||
|
self.pending_headers.sort(key=lambda x: x.get('block_height'))
|
||||||
|
# print "pending headers", map(lambda x: x.get('block_height'), self.pending_headers)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def verify_chunk(self, index, hexdata):
|
||||||
|
data = hexdata.decode('hex')
|
||||||
|
height = index*2016
|
||||||
|
num = len(data)/80
|
||||||
|
print_error("validating headers %d"%height)
|
||||||
|
|
||||||
|
if index == 0:
|
||||||
|
previous_hash = ("0"*64)
|
||||||
|
else:
|
||||||
|
prev_header = self.read_header(index*2016-1)
|
||||||
|
if prev_header is None: raise
|
||||||
|
previous_hash = self.hash_header(prev_header)
|
||||||
|
|
||||||
|
bits, target = self.get_target(index)
|
||||||
|
|
||||||
|
for i in range(num):
|
||||||
|
height = index*2016 + i
|
||||||
|
raw_header = data[i*80:(i+1)*80]
|
||||||
|
header = self.header_from_string(raw_header)
|
||||||
|
_hash = self.hash_header(header)
|
||||||
|
assert previous_hash == header.get('prev_block_hash')
|
||||||
|
assert bits == header.get('bits')
|
||||||
|
assert eval('0x'+_hash) < target
|
||||||
|
|
||||||
|
previous_header = header
|
||||||
|
previous_hash = _hash
|
||||||
|
|
||||||
|
self.save_chunk(index, data)
|
||||||
|
|
||||||
|
|
||||||
|
def verify_header(self, header):
|
||||||
|
# add header to the blockchain file
|
||||||
|
# if there is a reorg, push it in a stack
|
||||||
|
|
||||||
|
height = header.get('block_height')
|
||||||
|
|
||||||
|
prev_header = self.read_header(height -1)
|
||||||
|
if not prev_header:
|
||||||
|
# return False to request previous header
|
||||||
|
return False
|
||||||
|
|
||||||
|
prev_hash = self.hash_header(prev_header)
|
||||||
|
bits, target = self.get_target(height/2016)
|
||||||
|
_hash = self.hash_header(header)
|
||||||
|
try:
|
||||||
|
assert prev_hash == header.get('prev_block_hash')
|
||||||
|
assert bits == header.get('bits')
|
||||||
|
assert eval('0x'+_hash) < target
|
||||||
|
except:
|
||||||
|
# this can be caused by a reorg.
|
||||||
|
print_error("verify header failed"+ repr(header))
|
||||||
|
# undo verifications
|
||||||
|
with self.lock:
|
||||||
|
items = self.verified_tx.items()[:]
|
||||||
|
for tx_hash, item in items:
|
||||||
|
tx_height, timestamp, pos = item
|
||||||
|
if tx_height >= height:
|
||||||
|
print_error("redoing", tx_hash)
|
||||||
|
with self.lock:
|
||||||
|
self.verified_tx.pop(tx_hash)
|
||||||
|
if tx_hash in self.merkle_roots:
|
||||||
|
self.merkle_roots.pop(tx_hash)
|
||||||
|
# return False to request previous header.
|
||||||
|
return False
|
||||||
|
|
||||||
|
self.save_header(header)
|
||||||
|
print_error("verify header:", _hash, height)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def header_to_string(self, res):
|
||||||
|
s = int_to_hex(res.get('version'),4) \
|
||||||
|
+ rev_hex(res.get('prev_block_hash')) \
|
||||||
|
+ rev_hex(res.get('merkle_root')) \
|
||||||
|
+ int_to_hex(int(res.get('timestamp')),4) \
|
||||||
|
+ int_to_hex(int(res.get('bits')),4) \
|
||||||
|
+ int_to_hex(int(res.get('nonce')),4)
|
||||||
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def header_from_string(self, s):
|
||||||
|
hex_to_int = lambda s: eval('0x' + s[::-1].encode('hex'))
|
||||||
|
h = {}
|
||||||
|
h['version'] = hex_to_int(s[0:4])
|
||||||
|
h['prev_block_hash'] = hash_encode(s[4:36])
|
||||||
|
h['merkle_root'] = hash_encode(s[36:68])
|
||||||
|
h['timestamp'] = hex_to_int(s[68:72])
|
||||||
|
h['bits'] = hex_to_int(s[72:76])
|
||||||
|
h['nonce'] = hex_to_int(s[76:80])
|
||||||
|
return h
|
||||||
|
|
||||||
|
def hash_header(self, header):
|
||||||
|
return rev_hex(Hash(self.header_to_string(header).decode('hex')).encode('hex'))
|
||||||
|
|
||||||
|
def path(self):
|
||||||
|
wdir = self.config.get('blockchain_headers_path', user_dir())
|
||||||
|
if wdir and not os.path.exists( wdir ): os.mkdir(wdir)
|
||||||
|
return os.path.join( wdir, 'blockchain_headers')
|
||||||
|
|
||||||
|
def init_headers_file(self):
|
||||||
|
filename = self.path()
|
||||||
|
if os.path.exists(filename):
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
import urllib, socket
|
||||||
|
socket.setdefaulttimeout(30)
|
||||||
|
print_error("downloading ", self.headers_url )
|
||||||
|
urllib.urlretrieve(self.headers_url, filename)
|
||||||
|
except:
|
||||||
|
print_error( "download failed. creating file", filename )
|
||||||
|
open(filename,'wb+').close()
|
||||||
|
|
||||||
|
def save_chunk(self, index, chunk):
|
||||||
|
filename = self.path()
|
||||||
|
f = open(filename,'rb+')
|
||||||
|
f.seek(index*2016*80)
|
||||||
|
h = f.write(chunk)
|
||||||
|
f.close()
|
||||||
|
self.set_local_height()
|
||||||
|
|
||||||
|
def save_header(self, header):
|
||||||
|
data = self.header_to_string(header).decode('hex')
|
||||||
|
assert len(data) == 80
|
||||||
|
height = header.get('block_height')
|
||||||
|
filename = self.path()
|
||||||
|
f = open(filename,'rb+')
|
||||||
|
f.seek(height*80)
|
||||||
|
h = f.write(data)
|
||||||
|
f.close()
|
||||||
|
self.set_local_height()
|
||||||
|
|
||||||
|
|
||||||
|
def set_local_height(self):
|
||||||
|
name = self.path()
|
||||||
|
if os.path.exists(name):
|
||||||
|
h = os.path.getsize(name)/80 - 1
|
||||||
|
if self.local_height != h:
|
||||||
|
self.local_height = h
|
||||||
|
|
||||||
|
|
||||||
|
def read_header(self, block_height):
|
||||||
|
name = self.path()
|
||||||
|
if os.path.exists(name):
|
||||||
|
f = open(name,'rb')
|
||||||
|
f.seek(block_height*80)
|
||||||
|
h = f.read(80)
|
||||||
|
f.close()
|
||||||
|
if len(h) == 80:
|
||||||
|
h = self.header_from_string(h)
|
||||||
|
return h
|
||||||
|
|
||||||
|
|
||||||
|
def get_target(self, index):
|
||||||
|
|
||||||
|
max_target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
|
||||||
|
if index == 0: return 0x1d00ffff, max_target
|
||||||
|
|
||||||
|
first = self.read_header((index-1)*2016)
|
||||||
|
last = self.read_header(index*2016-1)
|
||||||
|
|
||||||
|
nActualTimespan = last.get('timestamp') - first.get('timestamp')
|
||||||
|
nTargetTimespan = 14*24*60*60
|
||||||
|
nActualTimespan = max(nActualTimespan, nTargetTimespan/4)
|
||||||
|
nActualTimespan = min(nActualTimespan, nTargetTimespan*4)
|
||||||
|
|
||||||
|
bits = last.get('bits')
|
||||||
|
# convert to bignum
|
||||||
|
MM = 256*256*256
|
||||||
|
a = bits%MM
|
||||||
|
if a < 0x8000:
|
||||||
|
a *= 256
|
||||||
|
target = (a) * pow(2, 8 * (bits/MM - 3))
|
||||||
|
|
||||||
|
# new target
|
||||||
|
new_target = min( max_target, (target * nActualTimespan)/nTargetTimespan )
|
||||||
|
|
||||||
|
# convert it to bits
|
||||||
|
c = ("%064X"%new_target)[2:]
|
||||||
|
i = 31
|
||||||
|
while c[0:2]=="00":
|
||||||
|
c = c[2:]
|
||||||
|
i -= 1
|
||||||
|
|
||||||
|
c = eval('0x'+c[0:6])
|
||||||
|
if c > 0x800000:
|
||||||
|
c /= 256
|
||||||
|
i += 1
|
||||||
|
|
||||||
|
new_bits = c + MM * i
|
||||||
|
return new_bits, new_target
|
||||||
|
|
|
@ -396,7 +396,7 @@ class Interface(threading.Thread):
|
||||||
self.unanswered_requests[self.message_id] = method, params, channel
|
self.unanswered_requests[self.message_id] = method, params, channel
|
||||||
ids.append(self.message_id)
|
ids.append(self.message_id)
|
||||||
# uncomment to debug
|
# uncomment to debug
|
||||||
# print "-->",request
|
# print "-->", request
|
||||||
self.message_id += 1
|
self.message_id += 1
|
||||||
out += request + '\n'
|
out += request + '\n'
|
||||||
while out:
|
while out:
|
||||||
|
|
|
@ -6,6 +6,9 @@ from version import ELECTRUM_VERSION, SEED_VERSION
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class SimpleConfig:
|
class SimpleConfig:
|
||||||
"""
|
"""
|
||||||
The SimpleConfig class is responsible for handling operations involving
|
The SimpleConfig class is responsible for handling operations involving
|
||||||
|
@ -29,46 +32,44 @@ a SimpleConfig instance then reads the wallet file.
|
||||||
# command-line options
|
# command-line options
|
||||||
self.options_config = options
|
self.options_config = options
|
||||||
|
|
||||||
self.wallet_config = {}
|
# init path
|
||||||
self.wallet_file_exists = False
|
self.init_path(options)
|
||||||
self.init_path(self.options_config.get('wallet_path'))
|
|
||||||
print_error( "path", self.path )
|
print_error( "user dir", self.user_dir)
|
||||||
if self.path:
|
|
||||||
self.read_wallet_config(self.path)
|
|
||||||
|
def init_path(self, options):
|
||||||
|
|
||||||
|
# Look for wallet file in the default data directory.
|
||||||
|
# Make wallet directory if it does not yet exist.
|
||||||
|
if not os.path.exists(self.user_dir):
|
||||||
|
os.mkdir(self.user_dir)
|
||||||
|
|
||||||
|
|
||||||
# portable wallet: use the same directory for wallet and headers file
|
# portable wallet: use the same directory for wallet and headers file
|
||||||
if options.get('portable'):
|
#if options.get('portable'):
|
||||||
self.wallet_config['blockchain_headers_path'] = os.path.dirname(self.path)
|
# self.wallet_config['blockchain_headers_path'] = os.path.dirname(self.path)
|
||||||
|
|
||||||
|
def set_key(self, key, value, save = True):
|
||||||
|
|
||||||
|
|
||||||
def set_key(self, key, value, save = False):
|
|
||||||
# find where a setting comes from and save it there
|
# find where a setting comes from and save it there
|
||||||
if self.options_config.get(key) is not None:
|
if self.options_config.get(key) is not None:
|
||||||
print "Warning: not changing '%s' because it was passed as a command-line option"%key
|
print "Warning: not changing '%s' because it was passed as a command-line option"%key
|
||||||
return
|
return
|
||||||
|
|
||||||
elif self.user_config.get(key) is not None:
|
|
||||||
self.user_config[key] = value
|
|
||||||
if save: self.save_user_config()
|
|
||||||
|
|
||||||
elif self.system_config.get(key) is not None:
|
elif self.system_config.get(key) is not None:
|
||||||
if str(self.system_config[key]) != str(value):
|
if str(self.system_config[key]) != str(value):
|
||||||
print "Warning: not changing '%s' because it was set in the system configuration"%key
|
print "Warning: not changing '%s' because it was set in the system configuration"%key
|
||||||
|
|
||||||
elif self.wallet_config.get(key) is not None:
|
|
||||||
self.wallet_config[key] = value
|
|
||||||
if save: self.save_wallet_config()
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# add key to wallet config
|
self.user_config[key] = value
|
||||||
self.wallet_config[key] = value
|
if save: self.save_user_config()
|
||||||
if save: self.save_wallet_config()
|
|
||||||
|
|
||||||
|
|
||||||
def get(self, key, default=None):
|
def get(self, key, default=None):
|
||||||
"""Retrieve the filepath of the configuration file specified in the 'key' parameter."""
|
|
||||||
|
out = None
|
||||||
|
|
||||||
# 1. command-line options always override everything
|
# 1. command-line options always override everything
|
||||||
if self.options_config.has_key(key) and self.options_config.get(key) is not None:
|
if self.options_config.has_key(key) and self.options_config.get(key) is not None:
|
||||||
out = self.options_config.get(key)
|
out = self.options_config.get(key)
|
||||||
|
@ -81,10 +82,6 @@ a SimpleConfig instance then reads the wallet file.
|
||||||
elif self.system_config.has_key(key):
|
elif self.system_config.has_key(key):
|
||||||
out = self.system_config.get(key)
|
out = self.system_config.get(key)
|
||||||
|
|
||||||
# 3. use the wallet file config
|
|
||||||
else:
|
|
||||||
out = self.wallet_config.get(key)
|
|
||||||
|
|
||||||
if out is None and default is not None:
|
if out is None and default is not None:
|
||||||
out = default
|
out = default
|
||||||
|
|
||||||
|
@ -135,85 +132,29 @@ a SimpleConfig instance then reads the wallet file.
|
||||||
"""Parse and store the user config settings in electrum.conf into user_config[]."""
|
"""Parse and store the user config settings in electrum.conf into user_config[]."""
|
||||||
if not self.user_dir: return
|
if not self.user_dir: return
|
||||||
|
|
||||||
name = os.path.join( self.user_dir, 'electrum.conf')
|
path = os.path.join(self.user_dir, "config")
|
||||||
if os.path.exists(name):
|
if os.path.exists(path):
|
||||||
try:
|
try:
|
||||||
import ConfigParser
|
with open(path, "r") as f:
|
||||||
except ImportError:
|
data = f.read()
|
||||||
print "cannot parse electrum.conf. please install ConfigParser"
|
except IOError:
|
||||||
return
|
return
|
||||||
|
|
||||||
p = ConfigParser.ConfigParser()
|
|
||||||
p.read(name)
|
|
||||||
try:
|
try:
|
||||||
for k, v in p.items('client'):
|
d = ast.literal_eval( data ) #parse raw data from reading wallet file
|
||||||
self.user_config[k] = v
|
except:
|
||||||
except ConfigParser.NoSectionError:
|
raise IOError("Cannot read config file.")
|
||||||
pass
|
|
||||||
|
|
||||||
def init_path(self, path):
|
self.user_config = d
|
||||||
"""Set the path of the wallet."""
|
|
||||||
|
|
||||||
if not path:
|
|
||||||
path = self.get('default_wallet_path')
|
|
||||||
|
|
||||||
if path is not None:
|
|
||||||
self.path = path
|
|
||||||
return
|
|
||||||
|
|
||||||
# Look for wallet file in the default data directory.
|
|
||||||
# Make wallet directory if it does not yet exist.
|
|
||||||
if not os.path.exists(self.user_dir):
|
|
||||||
os.mkdir(self.user_dir)
|
|
||||||
self.path = os.path.join(self.user_dir, "electrum.dat")
|
|
||||||
|
|
||||||
|
|
||||||
def save_user_config(self):
|
def save_user_config(self):
|
||||||
if not self.user_dir: return
|
if not self.user_dir: return
|
||||||
|
|
||||||
import ConfigParser
|
path = os.path.join(self.user_dir, "config")
|
||||||
config = ConfigParser.RawConfigParser()
|
s = repr(self.user_config)
|
||||||
config.add_section('client')
|
f = open(path,"w")
|
||||||
for k,v in self.user_config.items():
|
|
||||||
config.set('client', k, v)
|
|
||||||
|
|
||||||
with open( os.path.join( self.user_dir, 'electrum.conf'), 'wb') as configfile:
|
|
||||||
config.write(configfile)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def read_wallet_config(self, path):
|
|
||||||
"""Read the contents of the wallet file."""
|
|
||||||
try:
|
|
||||||
with open(self.path, "r") as f:
|
|
||||||
data = f.read()
|
|
||||||
except IOError:
|
|
||||||
return
|
|
||||||
try:
|
|
||||||
d = ast.literal_eval( data ) #parse raw data from reading wallet file
|
|
||||||
except:
|
|
||||||
raise IOError("Cannot read wallet file.")
|
|
||||||
|
|
||||||
self.wallet_config = d
|
|
||||||
self.wallet_file_exists = True
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def save(self, key=None):
|
|
||||||
self.save_wallet_config()
|
|
||||||
|
|
||||||
|
|
||||||
def save_wallet_config(self):
|
|
||||||
# prevent the creation of incomplete wallets
|
|
||||||
if self.wallet_config.get('master_public_keys') is None:
|
|
||||||
return
|
|
||||||
|
|
||||||
s = repr(self.wallet_config)
|
|
||||||
f = open(self.path,"w")
|
|
||||||
f.write( s )
|
f.write( s )
|
||||||
f.close()
|
f.close()
|
||||||
if self.get('gui') != 'android':
|
if self.get('gui') != 'android':
|
||||||
import stat
|
import stat
|
||||||
os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE)
|
os.chmod(path, stat.S_IREAD | stat.S_IWRITE)
|
||||||
|
|
||||||
|
|
297
lib/verifier.py
297
lib/verifier.py
|
@ -24,34 +24,29 @@ from bitcoin import *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WalletVerifier(threading.Thread):
|
class TxVerifier(threading.Thread):
|
||||||
""" Simple Payment Verification """
|
""" Simple Payment Verification """
|
||||||
|
|
||||||
def __init__(self, interface, config):
|
def __init__(self, interface, blockchain, storage):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self.daemon = True
|
self.daemon = True
|
||||||
self.config = config
|
self.storage = storage
|
||||||
|
self.blockchain = blockchain
|
||||||
self.interface = interface
|
self.interface = interface
|
||||||
self.transactions = {} # requested verifications (with height sent by the requestor)
|
self.transactions = {} # requested verifications (with height sent by the requestor)
|
||||||
self.interface.register_channel('verifier')
|
self.interface.register_channel('txverifier')
|
||||||
|
self.verified_tx = storage.get('verified_tx3',{}) # height, timestamp of verified transactions
|
||||||
self.verified_tx = config.get('verified_tx3',{}) # height, timestamp of verified transactions
|
self.merkle_roots = storage.get('merkle_roots',{}) # hashed by me
|
||||||
self.merkle_roots = config.get('merkle_roots',{}) # hashed by me
|
|
||||||
|
|
||||||
self.targets = config.get('targets',{}) # compute targets
|
|
||||||
self.lock = threading.Lock()
|
self.lock = threading.Lock()
|
||||||
self.pending_headers = [] # headers that have not been verified
|
|
||||||
self.height = 0
|
|
||||||
self.local_height = 0
|
|
||||||
self.running = False
|
self.running = False
|
||||||
self.headers_url = 'http://headers.electrum.org/blockchain_headers'
|
|
||||||
|
|
||||||
def get_confirmations(self, tx):
|
def get_confirmations(self, tx):
|
||||||
""" return the number of confirmations of a monitored transaction. """
|
""" return the number of confirmations of a monitored transaction. """
|
||||||
with self.lock:
|
with self.lock:
|
||||||
if tx in self.verified_tx:
|
if tx in self.verified_tx:
|
||||||
height, timestamp, pos = self.verified_tx[tx]
|
height, timestamp, pos = self.verified_tx[tx]
|
||||||
conf = (self.local_height - height + 1)
|
conf = (self.blockchain.local_height - height + 1)
|
||||||
if conf <= 0: timestamp = None
|
if conf <= 0: timestamp = None
|
||||||
|
|
||||||
elif tx in self.transactions:
|
elif tx in self.transactions:
|
||||||
|
@ -101,67 +96,21 @@ class WalletVerifier(threading.Thread):
|
||||||
with self.lock: return self.running
|
with self.lock: return self.running
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
|
|
||||||
self.init_headers_file()
|
|
||||||
self.set_local_height()
|
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.running = True
|
self.running = True
|
||||||
requested_merkle = []
|
requested_merkle = []
|
||||||
requested_chunks = []
|
|
||||||
requested_headers = []
|
|
||||||
all_chunks = False
|
|
||||||
|
|
||||||
# subscribe to block headers
|
|
||||||
self.interface.send([ ('blockchain.headers.subscribe',[])], 'verifier')
|
|
||||||
|
|
||||||
while self.is_running():
|
while self.is_running():
|
||||||
# request missing chunks
|
|
||||||
if not all_chunks and self.height and not requested_chunks:
|
|
||||||
|
|
||||||
if self.local_height + 50 < self.height:
|
|
||||||
min_index = (self.local_height + 1)/2016
|
|
||||||
max_index = (self.height + 1)/2016
|
|
||||||
for i in range(min_index, max_index + 1):
|
|
||||||
print_error( "requesting chunk", i )
|
|
||||||
self.interface.send([ ('blockchain.block.get_chunk',[i])], 'verifier')
|
|
||||||
requested_chunks.append(i)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
all_chunks = True
|
|
||||||
print_error("downloaded all chunks")
|
|
||||||
|
|
||||||
# request missing tx
|
# request missing tx
|
||||||
if all_chunks:
|
for tx_hash, tx_height in self.transactions.items():
|
||||||
for tx_hash, tx_height in self.transactions.items():
|
if tx_hash not in self.verified_tx:
|
||||||
if tx_hash not in self.verified_tx:
|
if self.merkle_roots.get(tx_hash) is None and tx_hash not in requested_merkle:
|
||||||
if self.merkle_roots.get(tx_hash) is None and tx_hash not in requested_merkle:
|
print_error('requesting merkle', tx_hash)
|
||||||
print_error('requesting merkle', tx_hash)
|
self.interface.send([ ('blockchain.transaction.get_merkle',[tx_hash, tx_height]) ], 'txverifier')
|
||||||
self.interface.send([ ('blockchain.transaction.get_merkle',[tx_hash, tx_height]) ], 'verifier')
|
requested_merkle.append(tx_hash)
|
||||||
requested_merkle.append(tx_hash)
|
|
||||||
|
|
||||||
# process pending headers
|
|
||||||
if self.pending_headers and all_chunks:
|
|
||||||
done = []
|
|
||||||
for header in self.pending_headers:
|
|
||||||
if self.verify_header(header):
|
|
||||||
done.append(header)
|
|
||||||
else:
|
|
||||||
# request previous header
|
|
||||||
i = header.get('block_height') - 1
|
|
||||||
if i not in requested_headers:
|
|
||||||
print_error("requesting header %d"%i)
|
|
||||||
self.interface.send([ ('blockchain.block.get_header',[i])], 'verifier')
|
|
||||||
requested_headers.append(i)
|
|
||||||
# no point continuing
|
|
||||||
break
|
|
||||||
if done:
|
|
||||||
self.interface.trigger_callback('updated')
|
|
||||||
for header in done:
|
|
||||||
self.pending_headers.remove(header)
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r = self.interface.get_response('verifier',timeout=1)
|
r = self.interface.get_response('txverifier',timeout=1)
|
||||||
except Queue.Empty:
|
except Queue.Empty:
|
||||||
continue
|
continue
|
||||||
if not r: continue
|
if not r: continue
|
||||||
|
@ -180,138 +129,23 @@ class WalletVerifier(threading.Thread):
|
||||||
self.verify_merkle(tx_hash, result)
|
self.verify_merkle(tx_hash, result)
|
||||||
requested_merkle.remove(tx_hash)
|
requested_merkle.remove(tx_hash)
|
||||||
|
|
||||||
elif method == 'blockchain.block.get_chunk':
|
|
||||||
index = params[0]
|
|
||||||
self.verify_chunk(index, result)
|
|
||||||
requested_chunks.remove(index)
|
|
||||||
|
|
||||||
elif method in ['blockchain.headers.subscribe', 'blockchain.block.get_header']:
|
|
||||||
|
|
||||||
self.pending_headers.append(result)
|
|
||||||
if method == 'blockchain.block.get_header':
|
|
||||||
requested_headers.remove(result.get('block_height'))
|
|
||||||
else:
|
|
||||||
self.height = result.get('block_height')
|
|
||||||
self.interface.poke('synchronizer')
|
|
||||||
|
|
||||||
self.pending_headers.sort(key=lambda x: x.get('block_height'))
|
|
||||||
# print "pending headers", map(lambda x: x.get('block_height'), self.pending_headers)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def verify_merkle(self, tx_hash, result):
|
def verify_merkle(self, tx_hash, result):
|
||||||
tx_height = result.get('block_height')
|
tx_height = result.get('block_height')
|
||||||
pos = result.get('pos')
|
pos = result.get('pos')
|
||||||
self.merkle_roots[tx_hash] = self.hash_merkle_root(result['merkle'], tx_hash, pos)
|
self.merkle_roots[tx_hash] = self.hash_merkle_root(result['merkle'], tx_hash, pos)
|
||||||
header = self.read_header(tx_height)
|
header = self.blockchain.read_header(tx_height)
|
||||||
if not header: return
|
if not header: return
|
||||||
assert header.get('merkle_root') == self.merkle_roots[tx_hash]
|
assert header.get('merkle_root') == self.merkle_roots[tx_hash]
|
||||||
# we passed all the tests
|
# we passed all the tests
|
||||||
header = self.read_header(tx_height)
|
|
||||||
timestamp = header.get('timestamp')
|
timestamp = header.get('timestamp')
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.verified_tx[tx_hash] = (tx_height, timestamp, pos)
|
self.verified_tx[tx_hash] = (tx_height, timestamp, pos)
|
||||||
print_error("verified %s"%tx_hash)
|
print_error("verified %s"%tx_hash)
|
||||||
self.config.set_key('verified_tx3', self.verified_tx, True)
|
self.storage.put('verified_tx3', self.verified_tx, True)
|
||||||
self.interface.trigger_callback('updated')
|
self.interface.trigger_callback('updated')
|
||||||
|
|
||||||
|
|
||||||
def verify_chunk(self, index, hexdata):
|
|
||||||
data = hexdata.decode('hex')
|
|
||||||
height = index*2016
|
|
||||||
num = len(data)/80
|
|
||||||
print_error("validating headers %d"%height)
|
|
||||||
|
|
||||||
if index == 0:
|
|
||||||
previous_hash = ("0"*64)
|
|
||||||
else:
|
|
||||||
prev_header = self.read_header(index*2016-1)
|
|
||||||
if prev_header is None: raise
|
|
||||||
previous_hash = self.hash_header(prev_header)
|
|
||||||
|
|
||||||
bits, target = self.get_target(index)
|
|
||||||
|
|
||||||
for i in range(num):
|
|
||||||
height = index*2016 + i
|
|
||||||
raw_header = data[i*80:(i+1)*80]
|
|
||||||
header = self.header_from_string(raw_header)
|
|
||||||
_hash = self.hash_header(header)
|
|
||||||
assert previous_hash == header.get('prev_block_hash')
|
|
||||||
assert bits == header.get('bits')
|
|
||||||
assert eval('0x'+_hash) < target
|
|
||||||
|
|
||||||
previous_header = header
|
|
||||||
previous_hash = _hash
|
|
||||||
|
|
||||||
self.save_chunk(index, data)
|
|
||||||
|
|
||||||
|
|
||||||
def verify_header(self, header):
|
|
||||||
# add header to the blockchain file
|
|
||||||
# if there is a reorg, push it in a stack
|
|
||||||
|
|
||||||
height = header.get('block_height')
|
|
||||||
|
|
||||||
prev_header = self.read_header(height -1)
|
|
||||||
if not prev_header:
|
|
||||||
# return False to request previous header
|
|
||||||
return False
|
|
||||||
|
|
||||||
prev_hash = self.hash_header(prev_header)
|
|
||||||
bits, target = self.get_target(height/2016)
|
|
||||||
_hash = self.hash_header(header)
|
|
||||||
try:
|
|
||||||
assert prev_hash == header.get('prev_block_hash')
|
|
||||||
assert bits == header.get('bits')
|
|
||||||
assert eval('0x'+_hash) < target
|
|
||||||
except:
|
|
||||||
# this can be caused by a reorg.
|
|
||||||
print_error("verify header failed"+ repr(header))
|
|
||||||
# undo verifications
|
|
||||||
with self.lock:
|
|
||||||
items = self.verified_tx.items()[:]
|
|
||||||
for tx_hash, item in items:
|
|
||||||
tx_height, timestamp, pos = item
|
|
||||||
if tx_height >= height:
|
|
||||||
print_error("redoing", tx_hash)
|
|
||||||
with self.lock:
|
|
||||||
self.verified_tx.pop(tx_hash)
|
|
||||||
if tx_hash in self.merkle_roots:
|
|
||||||
self.merkle_roots.pop(tx_hash)
|
|
||||||
# return False to request previous header.
|
|
||||||
return False
|
|
||||||
|
|
||||||
self.save_header(header)
|
|
||||||
print_error("verify header:", _hash, height)
|
|
||||||
return True
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def header_to_string(self, res):
|
|
||||||
s = int_to_hex(res.get('version'),4) \
|
|
||||||
+ rev_hex(res.get('prev_block_hash')) \
|
|
||||||
+ rev_hex(res.get('merkle_root')) \
|
|
||||||
+ int_to_hex(int(res.get('timestamp')),4) \
|
|
||||||
+ int_to_hex(int(res.get('bits')),4) \
|
|
||||||
+ int_to_hex(int(res.get('nonce')),4)
|
|
||||||
return s
|
|
||||||
|
|
||||||
|
|
||||||
def header_from_string(self, s):
|
|
||||||
hex_to_int = lambda s: eval('0x' + s[::-1].encode('hex'))
|
|
||||||
h = {}
|
|
||||||
h['version'] = hex_to_int(s[0:4])
|
|
||||||
h['prev_block_hash'] = hash_encode(s[4:36])
|
|
||||||
h['merkle_root'] = hash_encode(s[36:68])
|
|
||||||
h['timestamp'] = hex_to_int(s[68:72])
|
|
||||||
h['bits'] = hex_to_int(s[72:76])
|
|
||||||
h['nonce'] = hex_to_int(s[76:80])
|
|
||||||
return h
|
|
||||||
|
|
||||||
def hash_header(self, header):
|
|
||||||
return rev_hex(Hash(self.header_to_string(header).decode('hex')).encode('hex'))
|
|
||||||
|
|
||||||
def hash_merkle_root(self, merkle_s, target_hash, pos):
|
def hash_merkle_root(self, merkle_s, target_hash, pos):
|
||||||
h = hash_decode(target_hash)
|
h = hash_decode(target_hash)
|
||||||
for i in range(len(merkle_s)):
|
for i in range(len(merkle_s)):
|
||||||
|
@ -319,101 +153,6 @@ class WalletVerifier(threading.Thread):
|
||||||
h = Hash( hash_decode(item) + h ) if ((pos >> i) & 1) else Hash( h + hash_decode(item) )
|
h = Hash( hash_decode(item) + h ) if ((pos >> i) & 1) else Hash( h + hash_decode(item) )
|
||||||
return hash_encode(h)
|
return hash_encode(h)
|
||||||
|
|
||||||
def path(self):
|
|
||||||
wdir = self.config.get('blockchain_headers_path', user_dir())
|
|
||||||
if wdir and not os.path.exists( wdir ): os.mkdir(wdir)
|
|
||||||
return os.path.join( wdir, 'blockchain_headers')
|
|
||||||
|
|
||||||
def init_headers_file(self):
|
|
||||||
filename = self.path()
|
|
||||||
if os.path.exists(filename):
|
|
||||||
return
|
|
||||||
|
|
||||||
try:
|
|
||||||
import urllib, socket
|
|
||||||
socket.setdefaulttimeout(30)
|
|
||||||
print_error("downloading ", self.headers_url )
|
|
||||||
urllib.urlretrieve(self.headers_url, filename)
|
|
||||||
except:
|
|
||||||
print_error( "download failed. creating file", filename )
|
|
||||||
open(filename,'wb+').close()
|
|
||||||
|
|
||||||
def save_chunk(self, index, chunk):
|
|
||||||
filename = self.path()
|
|
||||||
f = open(filename,'rb+')
|
|
||||||
f.seek(index*2016*80)
|
|
||||||
h = f.write(chunk)
|
|
||||||
f.close()
|
|
||||||
self.set_local_height()
|
|
||||||
|
|
||||||
def save_header(self, header):
|
|
||||||
data = self.header_to_string(header).decode('hex')
|
|
||||||
assert len(data) == 80
|
|
||||||
height = header.get('block_height')
|
|
||||||
filename = self.path()
|
|
||||||
f = open(filename,'rb+')
|
|
||||||
f.seek(height*80)
|
|
||||||
h = f.write(data)
|
|
||||||
f.close()
|
|
||||||
self.set_local_height()
|
|
||||||
|
|
||||||
|
|
||||||
def set_local_height(self):
|
|
||||||
name = self.path()
|
|
||||||
if os.path.exists(name):
|
|
||||||
h = os.path.getsize(name)/80 - 1
|
|
||||||
if self.local_height != h:
|
|
||||||
self.local_height = h
|
|
||||||
|
|
||||||
|
|
||||||
def read_header(self, block_height):
|
|
||||||
name = self.path()
|
|
||||||
if os.path.exists(name):
|
|
||||||
f = open(name,'rb')
|
|
||||||
f.seek(block_height*80)
|
|
||||||
h = f.read(80)
|
|
||||||
f.close()
|
|
||||||
if len(h) == 80:
|
|
||||||
h = self.header_from_string(h)
|
|
||||||
return h
|
|
||||||
|
|
||||||
|
|
||||||
def get_target(self, index):
|
|
||||||
|
|
||||||
max_target = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
|
|
||||||
if index == 0: return 0x1d00ffff, max_target
|
|
||||||
|
|
||||||
first = self.read_header((index-1)*2016)
|
|
||||||
last = self.read_header(index*2016-1)
|
|
||||||
|
|
||||||
nActualTimespan = last.get('timestamp') - first.get('timestamp')
|
|
||||||
nTargetTimespan = 14*24*60*60
|
|
||||||
nActualTimespan = max(nActualTimespan, nTargetTimespan/4)
|
|
||||||
nActualTimespan = min(nActualTimespan, nTargetTimespan*4)
|
|
||||||
|
|
||||||
bits = last.get('bits')
|
|
||||||
# convert to bignum
|
|
||||||
MM = 256*256*256
|
|
||||||
a = bits%MM
|
|
||||||
if a < 0x8000:
|
|
||||||
a *= 256
|
|
||||||
target = (a) * pow(2, 8 * (bits/MM - 3))
|
|
||||||
|
|
||||||
# new target
|
|
||||||
new_target = min( max_target, (target * nActualTimespan)/nTargetTimespan )
|
|
||||||
|
|
||||||
# convert it to bits
|
|
||||||
c = ("%064X"%new_target)[2:]
|
|
||||||
i = 31
|
|
||||||
while c[0:2]=="00":
|
|
||||||
c = c[2:]
|
|
||||||
i -= 1
|
|
||||||
|
|
||||||
c = eval('0x'+c[0:6])
|
|
||||||
if c > 0x800000:
|
|
||||||
c /= 256
|
|
||||||
i += 1
|
|
||||||
|
|
||||||
new_bits = c + MM * i
|
|
||||||
return new_bits, new_target
|
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
ELECTRUM_VERSION = "1.9" # version of the client package
|
ELECTRUM_VERSION = "1.9" # version of the client package
|
||||||
PROTOCOL_VERSION = '0.6' # protocol version requested
|
PROTOCOL_VERSION = '0.6' # protocol version requested
|
||||||
SEED_VERSION = 4 # bump this every time the seed generation is modified
|
SEED_VERSION = 5 # bump this every time the seed generation is modified
|
||||||
TRANSLATION_ID = 4101 # version of the wiki page
|
TRANSLATION_ID = 4101 # version of the wiki page
|
||||||
|
|
258
lib/wallet.py
258
lib/wallet.py
|
@ -63,39 +63,113 @@ def pw_decode(s, password):
|
||||||
from version import ELECTRUM_VERSION, SEED_VERSION
|
from version import ELECTRUM_VERSION, SEED_VERSION
|
||||||
|
|
||||||
|
|
||||||
class Wallet:
|
class WalletStorage:
|
||||||
def __init__(self, config={}):
|
|
||||||
|
|
||||||
self.config = config
|
def __init__(self, config):
|
||||||
|
self.data = {}
|
||||||
|
self.file_exists = False
|
||||||
|
self.init_path(config)
|
||||||
|
print_error( "wallet path", self.path )
|
||||||
|
if self.path:
|
||||||
|
self.read(self.path)
|
||||||
|
|
||||||
|
|
||||||
|
def init_path(self, config):
|
||||||
|
"""Set the path of the wallet."""
|
||||||
|
|
||||||
|
path = config.get('wallet_path')
|
||||||
|
if not path:
|
||||||
|
path = config.get('default_wallet_path')
|
||||||
|
if path is not None:
|
||||||
|
self.path = path
|
||||||
|
return
|
||||||
|
|
||||||
|
# Look for wallet file in the default data directory.
|
||||||
|
# Make wallet directory if it does not yet exist.
|
||||||
|
if not os.path.exists(self.user_dir):
|
||||||
|
os.mkdir(self.user_dir)
|
||||||
|
|
||||||
|
self.path = os.path.join(self.user_dir, "electrum.dat")
|
||||||
|
|
||||||
|
|
||||||
|
def read(self, path):
|
||||||
|
"""Read the contents of the wallet file."""
|
||||||
|
try:
|
||||||
|
with open(self.path, "r") as f:
|
||||||
|
data = f.read()
|
||||||
|
except IOError:
|
||||||
|
return
|
||||||
|
try:
|
||||||
|
d = ast.literal_eval( data ) #parse raw data from reading wallet file
|
||||||
|
except:
|
||||||
|
raise IOError("Cannot read wallet file.")
|
||||||
|
|
||||||
|
self.data = d
|
||||||
|
self.file_exists = True
|
||||||
|
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
return self.data.get(key, default)
|
||||||
|
|
||||||
|
def put(self, key, value, save = True):
|
||||||
|
|
||||||
|
if self.data.get(key) is not None:
|
||||||
|
self.data[key] = value
|
||||||
|
else:
|
||||||
|
# add key to wallet config
|
||||||
|
self.data[key] = value
|
||||||
|
|
||||||
|
if save:
|
||||||
|
self.write()
|
||||||
|
|
||||||
|
|
||||||
|
def write(self):
|
||||||
|
s = repr(self.data)
|
||||||
|
f = open(self.path,"w")
|
||||||
|
f.write( s )
|
||||||
|
f.close()
|
||||||
|
if self.get('gui') != 'android':
|
||||||
|
import stat
|
||||||
|
os.chmod(self.path,stat.S_IREAD | stat.S_IWRITE)
|
||||||
|
|
||||||
|
|
||||||
|
class Wallet:
|
||||||
|
|
||||||
|
def __init__(self, storage):
|
||||||
|
|
||||||
|
self.storage = storage
|
||||||
self.electrum_version = ELECTRUM_VERSION
|
self.electrum_version = ELECTRUM_VERSION
|
||||||
self.gap_limit_for_change = 3 # constant
|
self.gap_limit_for_change = 3 # constant
|
||||||
|
|
||||||
# saved fields
|
# saved fields
|
||||||
self.seed_version = config.get('seed_version', SEED_VERSION)
|
self.seed_version = storage.get('seed_version', SEED_VERSION)
|
||||||
self.gap_limit = config.get('gap_limit', 5)
|
|
||||||
self.use_change = config.get('use_change',True)
|
|
||||||
self.fee = int(config.get('fee_per_kb',20000))
|
|
||||||
self.num_zeros = int(config.get('num_zeros',0))
|
|
||||||
self.use_encryption = config.get('use_encryption', False)
|
|
||||||
self.seed = config.get('seed', '') # encrypted
|
|
||||||
self.labels = config.get('labels', {})
|
|
||||||
self.frozen_addresses = config.get('frozen_addresses',[])
|
|
||||||
self.prioritized_addresses = config.get('prioritized_addresses',[])
|
|
||||||
self.addressbook = config.get('contacts', [])
|
|
||||||
|
|
||||||
self.imported_keys = config.get('imported_keys',{})
|
self.gap_limit = storage.get('gap_limit', 5)
|
||||||
self.history = config.get('addr_history',{}) # address -> list(txid, height)
|
self.use_change = storage.get('use_change',True)
|
||||||
|
self.use_encryption = storage.get('use_encryption', False)
|
||||||
|
self.seed = storage.get('seed', '') # encrypted
|
||||||
|
self.labels = storage.get('labels', {})
|
||||||
|
self.frozen_addresses = storage.get('frozen_addresses',[])
|
||||||
|
self.prioritized_addresses = storage.get('prioritized_addresses',[])
|
||||||
|
self.addressbook = storage.get('contacts', [])
|
||||||
|
|
||||||
|
self.imported_keys = storage.get('imported_keys',{})
|
||||||
|
self.history = storage.get('addr_history',{}) # address -> list(txid, height)
|
||||||
|
|
||||||
self.master_public_keys = config.get('master_public_keys',{})
|
self.fee = int(storage.get('fee_per_kb',20000))
|
||||||
self.master_private_keys = config.get('master_private_keys', {})
|
|
||||||
|
|
||||||
self.first_addresses = config.get('first_addresses',{})
|
self.master_public_keys = storage.get('master_public_keys',{})
|
||||||
|
self.master_private_keys = storage.get('master_private_keys', {})
|
||||||
|
|
||||||
self.load_accounts(config)
|
self.first_addresses = storage.get('first_addresses',{})
|
||||||
|
|
||||||
|
#if self.seed_version != SEED_VERSION:
|
||||||
|
# raise ValueError("This wallet seed is deprecated. Please restore from seed.")
|
||||||
|
|
||||||
|
self.load_accounts()
|
||||||
|
|
||||||
self.transactions = {}
|
self.transactions = {}
|
||||||
tx = config.get('transactions',{})
|
tx = storage.get('transactions',{})
|
||||||
try:
|
try:
|
||||||
for k,v in tx.items(): self.transactions[k] = Transaction(v)
|
for k,v in tx.items(): self.transactions[k] = Transaction(v)
|
||||||
except:
|
except:
|
||||||
|
@ -117,9 +191,6 @@ class Wallet:
|
||||||
self.transaction_lock = threading.Lock()
|
self.transaction_lock = threading.Lock()
|
||||||
self.tx_event = threading.Event()
|
self.tx_event = threading.Event()
|
||||||
|
|
||||||
if self.seed_version != SEED_VERSION:
|
|
||||||
raise ValueError("This wallet seed is deprecated. Please run upgrade.py for a diagnostic.")
|
|
||||||
|
|
||||||
for tx_hash, tx in self.transactions.items():
|
for tx_hash, tx in self.transactions.items():
|
||||||
if self.check_new_tx(tx_hash, tx):
|
if self.check_new_tx(tx_hash, tx):
|
||||||
self.update_tx_outputs(tx_hash)
|
self.update_tx_outputs(tx_hash)
|
||||||
|
@ -152,13 +223,13 @@ class Wallet:
|
||||||
|
|
||||||
# store the originally requested keypair into the imported keys table
|
# store the originally requested keypair into the imported keys table
|
||||||
self.imported_keys[address] = pw_encode(sec, password )
|
self.imported_keys[address] = pw_encode(sec, password )
|
||||||
self.config.set_key('imported_keys', self.imported_keys, True)
|
self.storage.put('imported_keys', self.imported_keys, True)
|
||||||
return address
|
return address
|
||||||
|
|
||||||
def delete_imported_key(self, addr):
|
def delete_imported_key(self, addr):
|
||||||
if addr in self.imported_keys:
|
if addr in self.imported_keys:
|
||||||
self.imported_keys.pop(addr)
|
self.imported_keys.pop(addr)
|
||||||
self.config.set_key('imported_keys', self.imported_keys, True)
|
self.storage.put('imported_keys', self.imported_keys, True)
|
||||||
|
|
||||||
|
|
||||||
def init_seed(self, seed):
|
def init_seed(self, seed):
|
||||||
|
@ -169,8 +240,8 @@ class Wallet:
|
||||||
|
|
||||||
|
|
||||||
def save_seed(self):
|
def save_seed(self):
|
||||||
self.config.set_key('seed', self.seed, True)
|
self.storage.put('seed', self.seed, True)
|
||||||
self.config.set_key('seed_version', self.seed_version, True)
|
self.storage.put('seed_version', self.seed_version, True)
|
||||||
|
|
||||||
master_k, master_c, master_K, master_cK = bip32_init(self.seed)
|
master_k, master_c, master_K, master_cK = bip32_init(self.seed)
|
||||||
|
|
||||||
|
@ -202,8 +273,8 @@ class Wallet:
|
||||||
"m/5'/": k5
|
"m/5'/": k5
|
||||||
}
|
}
|
||||||
|
|
||||||
self.config.set_key('master_public_keys', self.master_public_keys, True)
|
self.storage.put('master_public_keys', self.master_public_keys, True)
|
||||||
self.config.set_key('master_private_keys', self.master_private_keys, True)
|
self.storage.put('master_private_keys', self.master_private_keys, True)
|
||||||
|
|
||||||
# create default account
|
# create default account
|
||||||
self.create_account('1','Main account')
|
self.create_account('1','Main account')
|
||||||
|
@ -220,14 +291,14 @@ class Wallet:
|
||||||
# for safety, we ask the user to enter their seed
|
# for safety, we ask the user to enter their seed
|
||||||
assert seed == self.decode_seed(password)
|
assert seed == self.decode_seed(password)
|
||||||
self.seed = ''
|
self.seed = ''
|
||||||
self.config.set_key('seed', '', True)
|
self.storage.put('seed', '', True)
|
||||||
|
|
||||||
|
|
||||||
def deseed_branch(self, k):
|
def deseed_branch(self, k):
|
||||||
# check that parent has no seed
|
# check that parent has no seed
|
||||||
assert self.seed == ''
|
assert self.seed == ''
|
||||||
self.master_private_keys.pop(k)
|
self.master_private_keys.pop(k)
|
||||||
self.config.set_key('master_private_keys', self.master_private_keys, True)
|
self.storage.put('master_private_keys', self.master_private_keys, True)
|
||||||
|
|
||||||
|
|
||||||
def account_id(self, account_type, i):
|
def account_id(self, account_type, i):
|
||||||
|
@ -260,7 +331,7 @@ class Wallet:
|
||||||
account_id, account = self.next_account(account_type)
|
account_id, account = self.next_account(account_type)
|
||||||
addr = account.first_address()
|
addr = account.first_address()
|
||||||
self.first_addresses[k] = addr
|
self.first_addresses[k] = addr
|
||||||
self.config.set_key('first_addresses',self.first_addresses)
|
self.storage.put('first_addresses',self.first_addresses)
|
||||||
|
|
||||||
return addr
|
return addr
|
||||||
|
|
||||||
|
@ -294,26 +365,39 @@ class Wallet:
|
||||||
return account_id, account
|
return account_id, account
|
||||||
|
|
||||||
|
|
||||||
def create_account(self, account_type = '1', name = 'unnamed'):
|
def create_account(self, account_type = '1', name = None):
|
||||||
account_id, account = self.next_account(account_type)
|
account_id, account = self.next_account(account_type)
|
||||||
self.accounts[account_id] = account
|
self.accounts[account_id] = account
|
||||||
self.save_accounts()
|
self.save_accounts()
|
||||||
self.labels[account_id] = name
|
if name:
|
||||||
self.config.set_key('labels', self.labels, True)
|
self.labels[account_id] = name
|
||||||
|
self.storage.put('labels', self.labels, True)
|
||||||
|
|
||||||
|
|
||||||
|
def create_old_account(self):
|
||||||
|
print self.seed
|
||||||
|
mpk = OldAccount.mpk_from_seed(self.seed)
|
||||||
|
self.storage.put('master_public_key', mpk, True)
|
||||||
|
self.accounts[0] = OldAccount({'mpk':mpk, 0:[], 1:[]})
|
||||||
|
self.save_accounts()
|
||||||
|
|
||||||
|
|
||||||
def save_accounts(self):
|
def save_accounts(self):
|
||||||
d = {}
|
d = {}
|
||||||
for k, v in self.accounts.items():
|
for k, v in self.accounts.items():
|
||||||
d[k] = v.dump()
|
d[k] = v.dump()
|
||||||
self.config.set_key('accounts', d, True)
|
self.storage.put('accounts', d, True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def load_accounts(self, config):
|
def load_accounts(self):
|
||||||
d = config.get('accounts', {})
|
d = self.storage.get('accounts', {})
|
||||||
self.accounts = {}
|
self.accounts = {}
|
||||||
for k, v in d.items():
|
for k, v in d.items():
|
||||||
if '&' in k:
|
if k == 0:
|
||||||
|
v['mpk'] = self.storage.get('master_public_key')
|
||||||
|
self.accounts[k] = OldAccount(v)
|
||||||
|
elif '&' in k:
|
||||||
self.accounts[k] = BIP32_Account_2of2(v)
|
self.accounts[k] = BIP32_Account_2of2(v)
|
||||||
else:
|
else:
|
||||||
self.accounts[k] = BIP32_Account(v)
|
self.accounts[k] = BIP32_Account(v)
|
||||||
|
@ -339,7 +423,7 @@ class Wallet:
|
||||||
|
|
||||||
def get_master_public_key(self):
|
def get_master_public_key(self):
|
||||||
raise
|
raise
|
||||||
return self.config.get("master_public_key")
|
return self.storage.get("master_public_key")
|
||||||
|
|
||||||
def get_master_private_key(self, account, password):
|
def get_master_private_key(self, account, password):
|
||||||
master_k = pw_decode( self.master_private_keys[account], password)
|
master_k = pw_decode( self.master_private_keys[account], password)
|
||||||
|
@ -379,6 +463,9 @@ class Wallet:
|
||||||
|
|
||||||
|
|
||||||
def get_keyID(self, account, sequence):
|
def get_keyID(self, account, sequence):
|
||||||
|
if account == 0:
|
||||||
|
return 'old'
|
||||||
|
|
||||||
rs = self.rebase_sequence(account, sequence)
|
rs = self.rebase_sequence(account, sequence)
|
||||||
dd = []
|
dd = []
|
||||||
for root, public_sequence in rs:
|
for root, public_sequence in rs:
|
||||||
|
@ -405,6 +492,12 @@ class Wallet:
|
||||||
out.append( pw_decode( self.imported_keys[address], password ) )
|
out.append( pw_decode( self.imported_keys[address], password ) )
|
||||||
else:
|
else:
|
||||||
account, sequence = self.get_address_index(address)
|
account, sequence = self.get_address_index(address)
|
||||||
|
if account == 0:
|
||||||
|
seed = self.decode_seed(password)
|
||||||
|
pk = self.accounts[account].get_private_key(seed, sequence)
|
||||||
|
out.append(pk)
|
||||||
|
return out
|
||||||
|
|
||||||
# assert address == self.accounts[account].get_address(*sequence)
|
# assert address == self.accounts[account].get_address(*sequence)
|
||||||
rs = self.rebase_sequence( account, sequence)
|
rs = self.rebase_sequence( account, sequence)
|
||||||
for root, public_sequence in rs:
|
for root, public_sequence in rs:
|
||||||
|
@ -520,7 +613,7 @@ class Wallet:
|
||||||
def change_gap_limit(self, value):
|
def change_gap_limit(self, value):
|
||||||
if value >= self.gap_limit:
|
if value >= self.gap_limit:
|
||||||
self.gap_limit = value
|
self.gap_limit = value
|
||||||
self.config.set_key('gap_limit', self.gap_limit, True)
|
self.storage.put('gap_limit', self.gap_limit, True)
|
||||||
self.interface.poke('synchronizer')
|
self.interface.poke('synchronizer')
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
@ -533,7 +626,7 @@ class Wallet:
|
||||||
self.accounts[key][0] = addresses
|
self.accounts[key][0] = addresses
|
||||||
|
|
||||||
self.gap_limit = value
|
self.gap_limit = value
|
||||||
self.config.set_key('gap_limit', self.gap_limit, True)
|
self.storage.put('gap_limit', self.gap_limit, True)
|
||||||
self.save_accounts()
|
self.save_accounts()
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
|
@ -616,13 +709,14 @@ class Wallet:
|
||||||
|
|
||||||
|
|
||||||
def synchronize(self):
|
def synchronize(self):
|
||||||
self.create_pending_accounts()
|
if self.master_public_keys:
|
||||||
|
self.create_pending_accounts()
|
||||||
new = []
|
new = []
|
||||||
for account in self.accounts.values():
|
for account in self.accounts.values():
|
||||||
new += self.synchronize_account(account)
|
new += self.synchronize_account(account)
|
||||||
if new:
|
if new:
|
||||||
self.save_accounts()
|
self.save_accounts()
|
||||||
self.config.set_key('addr_history', self.history, True)
|
self.storage.put('addr_history', self.history, True)
|
||||||
return new
|
return new
|
||||||
|
|
||||||
|
|
||||||
|
@ -632,15 +726,15 @@ class Wallet:
|
||||||
|
|
||||||
def add_contact(self, address, label=None):
|
def add_contact(self, address, label=None):
|
||||||
self.addressbook.append(address)
|
self.addressbook.append(address)
|
||||||
self.config.set_key('contacts', self.addressbook, True)
|
self.storage.put('contacts', self.addressbook, True)
|
||||||
if label:
|
if label:
|
||||||
self.labels[address] = label
|
self.labels[address] = label
|
||||||
self.config.set_key('labels', self.labels, True)
|
self.storage.put('labels', self.labels, True)
|
||||||
|
|
||||||
def delete_contact(self, addr):
|
def delete_contact(self, addr):
|
||||||
if addr in self.addressbook:
|
if addr in self.addressbook:
|
||||||
self.addressbook.remove(addr)
|
self.addressbook.remove(addr)
|
||||||
self.config.set_key('addressbook', self.addressbook, True)
|
self.storage.put('addressbook', self.addressbook, True)
|
||||||
|
|
||||||
|
|
||||||
def fill_addressbook(self):
|
def fill_addressbook(self):
|
||||||
|
@ -837,6 +931,11 @@ class Wallet:
|
||||||
return inputs, total, fee
|
return inputs, total, fee
|
||||||
|
|
||||||
|
|
||||||
|
def set_fee(self, fee):
|
||||||
|
if self.fee != fee:
|
||||||
|
self.fee = fee
|
||||||
|
self.storage.put('fee_per_kb', self.fee, True)
|
||||||
|
|
||||||
def estimated_fee(self, inputs):
|
def estimated_fee(self, inputs):
|
||||||
estimated_size = len(inputs) * 180 + 80 # this assumes non-compressed keys
|
estimated_size = len(inputs) * 180 + 80 # this assumes non-compressed keys
|
||||||
fee = self.fee * int(round(estimated_size/1024.))
|
fee = self.fee * int(round(estimated_size/1024.))
|
||||||
|
@ -900,7 +999,7 @@ class Wallet:
|
||||||
tx = {}
|
tx = {}
|
||||||
for k,v in self.transactions.items():
|
for k,v in self.transactions.items():
|
||||||
tx[k] = str(v)
|
tx[k] = str(v)
|
||||||
self.config.set_key('transactions', tx, True)
|
self.storage.put('transactions', tx, True)
|
||||||
|
|
||||||
def receive_history_callback(self, addr, hist):
|
def receive_history_callback(self, addr, hist):
|
||||||
|
|
||||||
|
@ -909,7 +1008,7 @@ class Wallet:
|
||||||
|
|
||||||
with self.lock:
|
with self.lock:
|
||||||
self.history[addr] = hist
|
self.history[addr] = hist
|
||||||
self.config.set_key('addr_history', self.history, True)
|
self.storage.put('addr_history', self.history, True)
|
||||||
|
|
||||||
if hist != ['*']:
|
if hist != ['*']:
|
||||||
for tx_hash, tx_height in hist:
|
for tx_hash, tx_height in hist:
|
||||||
|
@ -1060,28 +1159,28 @@ class Wallet:
|
||||||
if new_password == '': new_password = None
|
if new_password == '': new_password = None
|
||||||
# this will throw an exception if unicode cannot be converted
|
# this will throw an exception if unicode cannot be converted
|
||||||
self.seed = pw_encode( seed, new_password)
|
self.seed = pw_encode( seed, new_password)
|
||||||
self.config.set_key('seed', self.seed, True)
|
self.storage.put('seed', self.seed, True)
|
||||||
self.use_encryption = (new_password != None)
|
self.use_encryption = (new_password != None)
|
||||||
self.config.set_key('use_encryption', self.use_encryption,True)
|
self.storage.put('use_encryption', self.use_encryption,True)
|
||||||
for k in self.imported_keys.keys():
|
for k in self.imported_keys.keys():
|
||||||
a = self.imported_keys[k]
|
a = self.imported_keys[k]
|
||||||
b = pw_decode(a, old_password)
|
b = pw_decode(a, old_password)
|
||||||
c = pw_encode(b, new_password)
|
c = pw_encode(b, new_password)
|
||||||
self.imported_keys[k] = c
|
self.imported_keys[k] = c
|
||||||
self.config.set_key('imported_keys', self.imported_keys, True)
|
self.storage.put('imported_keys', self.imported_keys, True)
|
||||||
|
|
||||||
for k, v in self.master_private_keys.items():
|
for k, v in self.master_private_keys.items():
|
||||||
b = pw_decode(v, old_password)
|
b = pw_decode(v, old_password)
|
||||||
c = pw_encode(b, new_password)
|
c = pw_encode(b, new_password)
|
||||||
self.master_private_keys[k] = c
|
self.master_private_keys[k] = c
|
||||||
self.config.set_key('master_private_keys', self.master_private_keys, True)
|
self.storage.put('master_private_keys', self.master_private_keys, True)
|
||||||
|
|
||||||
|
|
||||||
def freeze(self,addr):
|
def freeze(self,addr):
|
||||||
if self.is_mine(addr) and addr not in self.frozen_addresses:
|
if self.is_mine(addr) and addr not in self.frozen_addresses:
|
||||||
self.unprioritize(addr)
|
self.unprioritize(addr)
|
||||||
self.frozen_addresses.append(addr)
|
self.frozen_addresses.append(addr)
|
||||||
self.config.set_key('frozen_addresses', self.frozen_addresses, True)
|
self.storage.put('frozen_addresses', self.frozen_addresses, True)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@ -1089,7 +1188,7 @@ class Wallet:
|
||||||
def unfreeze(self,addr):
|
def unfreeze(self,addr):
|
||||||
if self.is_mine(addr) and addr in self.frozen_addresses:
|
if self.is_mine(addr) and addr in self.frozen_addresses:
|
||||||
self.frozen_addresses.remove(addr)
|
self.frozen_addresses.remove(addr)
|
||||||
self.config.set_key('frozen_addresses', self.frozen_addresses, True)
|
self.storage.put('frozen_addresses', self.frozen_addresses, True)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@ -1098,7 +1197,7 @@ class Wallet:
|
||||||
if self.is_mine(addr) and addr not in self.prioritized_addresses:
|
if self.is_mine(addr) and addr not in self.prioritized_addresses:
|
||||||
self.unfreeze(addr)
|
self.unfreeze(addr)
|
||||||
self.prioritized_addresses.append(addr)
|
self.prioritized_addresses.append(addr)
|
||||||
self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
|
self.storage.put('prioritized_addresses', self.prioritized_addresses, True)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
@ -1106,38 +1205,11 @@ class Wallet:
|
||||||
def unprioritize(self,addr):
|
def unprioritize(self,addr):
|
||||||
if self.is_mine(addr) and addr in self.prioritized_addresses:
|
if self.is_mine(addr) and addr in self.prioritized_addresses:
|
||||||
self.prioritized_addresses.remove(addr)
|
self.prioritized_addresses.remove(addr)
|
||||||
self.config.set_key('prioritized_addresses', self.prioritized_addresses, True)
|
self.storage.put('prioritized_addresses', self.prioritized_addresses, True)
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def set_fee(self, fee):
|
|
||||||
if self.fee != fee:
|
|
||||||
self.fee = fee
|
|
||||||
self.config.set_key('fee_per_kb', self.fee, True)
|
|
||||||
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
print_error("Warning: wallet.save() is deprecated")
|
|
||||||
tx = {}
|
|
||||||
for k,v in self.transactions.items():
|
|
||||||
tx[k] = str(v)
|
|
||||||
|
|
||||||
s = {
|
|
||||||
'use_change': self.use_change,
|
|
||||||
'fee_per_kb': self.fee,
|
|
||||||
'addr_history': self.history,
|
|
||||||
'labels': self.labels,
|
|
||||||
'contacts': self.addressbook,
|
|
||||||
'num_zeros': self.num_zeros,
|
|
||||||
'frozen_addresses': self.frozen_addresses,
|
|
||||||
'prioritized_addresses': self.prioritized_addresses,
|
|
||||||
'gap_limit': self.gap_limit,
|
|
||||||
'transactions': tx,
|
|
||||||
}
|
|
||||||
for k, v in s.items():
|
|
||||||
self.config.set_key(k,v)
|
|
||||||
self.config.save()
|
|
||||||
|
|
||||||
def set_verifier(self, verifier):
|
def set_verifier(self, verifier):
|
||||||
self.verifier = verifier
|
self.verifier = verifier
|
||||||
|
@ -1239,12 +1311,26 @@ class Wallet:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def start_threads(self, interface, blockchain):
|
||||||
|
from verifier import TxVerifier
|
||||||
|
self.interface = interface
|
||||||
|
self.verifier = TxVerifier(interface, blockchain, self.storage)
|
||||||
|
self.verifier.start()
|
||||||
|
self.set_verifier(self.verifier)
|
||||||
|
self.synchronizer = WalletSynchronizer(self)
|
||||||
|
self.synchronizer.start()
|
||||||
|
|
||||||
|
def stop_threads(self):
|
||||||
|
self.verifier.stop()
|
||||||
|
self.synchronizer.stop()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WalletSynchronizer(threading.Thread):
|
class WalletSynchronizer(threading.Thread):
|
||||||
|
|
||||||
|
|
||||||
def __init__(self, wallet, config):
|
def __init__(self, wallet):
|
||||||
threading.Thread.__init__(self)
|
threading.Thread.__init__(self)
|
||||||
self.daemon = True
|
self.daemon = True
|
||||||
self.wallet = wallet
|
self.wallet = wallet
|
||||||
|
|
1
setup.py
1
setup.py
|
@ -58,6 +58,7 @@ setup(name = "Electrum",
|
||||||
'electrum.wallet_bitkey',
|
'electrum.wallet_bitkey',
|
||||||
'electrum.wallet_factory',
|
'electrum.wallet_factory',
|
||||||
'electrum.interface',
|
'electrum.interface',
|
||||||
|
'electrum.blockchain',
|
||||||
'electrum.commands',
|
'electrum.commands',
|
||||||
'electrum.mnemonic',
|
'electrum.mnemonic',
|
||||||
'electrum.simple_config',
|
'electrum.simple_config',
|
||||||
|
|
Loading…
Reference in New Issue