electrum

Electrum Bitcoin wallet
git clone https://git.parazyd.org/electrum
Log | Files | Refs | Submodules

commit 73b023e9675461e5bd429d18990514e038f49525
parent 92b392a56b719830577579d719526cfdee5a9e66
Author: ThomasV <thomasv@electrum.org>
Date:   Mon, 10 Jul 2017 13:51:13 +0200

update network settings dialog of the kivy GUI

Diffstat:
Mgui/kivy/main.kv | 33++++++++++++++++++++++++++++++++-
Mgui/kivy/main_window.py | 85+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mgui/kivy/uix/dialogs/amount_dialog.py | 2+-
Dgui/kivy/uix/dialogs/blockchain_dialog.py | 63---------------------------------------------------------------
Mgui/kivy/uix/dialogs/settings.py | 132++++++++++++++-----------------------------------------------------------------
Mgui/kivy/uix/ui_screens/network.kv | 117+++++++++++++++++++++++++++++++++----------------------------------------------
Mgui/kivy/uix/ui_screens/proxy.kv | 30++++++++++++++++++++----------
Agui/kivy/uix/ui_screens/server.kv | 63+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mgui/qt/network_dialog.py | 4----
Mlib/network.py | 7+++++++
10 files changed, 276 insertions(+), 260 deletions(-)

diff --git a/gui/kivy/main.kv b/gui/kivy/main.kv @@ -293,6 +293,34 @@ size: self.width - sp(4), self.height - sp(4) +<SettingsItem@ButtonBehavior+BoxLayout> + orientation: 'vertical' + title: '' + description: '' + size_hint: 1, None + height: '60dp' + canvas.before: + Color: + rgba: (0.192, .498, 0.745, 1) if self.state == 'down' else (0.3, 0.3, 0.3, 0) + Rectangle: + size: self.size + pos: self.pos + on_release: + Clock.schedule_once(self.action) + Widget + TopLabel: + id: title + text: self.parent.title + bold: True + halign: 'left' + TopLabel: + text: self.parent.description + color: 0.8, 0.8, 0.8, 1 + halign: 'left' + Widget + + + <ScreenTabs@Screen> TabbedCarousel: @@ -365,7 +393,7 @@ BoxLayout: app_icon_width: '100dp' with_previous: False size_hint_x: None - on_release: app.popup_dialog('status') + on_release: app.popup_dialog('network') ActionButton: id: action_status @@ -387,6 +415,9 @@ BoxLayout: name: 'wallets' text: _('Wallets') ActionOvrButton: + name: 'network' + text: _('Network') + ActionOvrButton: name: 'settings' text: _('Settings') on_parent: diff --git a/gui/kivy/main_window.py b/gui/kivy/main_window.py @@ -73,10 +73,55 @@ from electrum.util import base_units class ElectrumWindow(App): electrum_config = ObjectProperty(None) - language = StringProperty('en') + + # properties might be updated by the network num_blocks = NumericProperty(0) num_nodes = NumericProperty(0) + server_host = StringProperty('') + server_port = StringProperty('') + + auto_connect = BooleanProperty(False) + def on_auto_connect(self, instance, x): + host, port, protocol, proxy, auto_connect = self.network.get_parameters() + self.network.set_parameters(host, port, protocol, proxy, self.auto_connect) + def toggle_auto_connect(self, x): + self.auto_connect = not self.auto_connect + + def choose_server_dialog(self, popup): + from uix.dialogs.choice_dialog import ChoiceDialog + protocol = 's' + def cb2(host): + from electrum.network import DEFAULT_PORTS + pp = servers.get(host, DEFAULT_PORTS) + port = pp.get(protocol, '') + popup.ids.host.text = host + popup.ids.port.text = port + servers = self.network.get_servers() + ChoiceDialog(_('Choose a server'), sorted(servers), popup.ids.host.text, cb2).open() + + def choose_blockchain_dialog(self, dt): + from uix.dialogs.choice_dialog import ChoiceDialog + def cb(name): + for index, b in self.network.blockchains.items(): + if name == self.network.get_blockchain_name(b): + self.network.follow_chain(index) + #self.block + names = [self.network.get_blockchain_name(b) for b in self.network.blockchains.values()] + if len(names) >1: + ChoiceDialog(_('Choose your chain'), names, '', cb).open() + + use_rbf = BooleanProperty(False) + def on_use_rbf(self, instance, x): + self.electrum_config.set_key('use_rbf', self.use_rbf, True) + + use_change = BooleanProperty(False) + def on_use_change(self, instance, x): + self.electrum_config.set_key('use_change', self.use_change, True) + + use_unconfirmed = BooleanProperty(False) + def on_use_unconfirmed(self, instance, x): + self.electrum_config.set_key('confirmed_only', not self.use_unconfirmed, True) def set_URI(self, uri): self.switch_to('send') @@ -195,13 +240,26 @@ class ElectrumWindow(App): title = _('Electrum App') self.electrum_config = config = kwargs.get('config', None) self.language = config.get('language', 'en') + self.network = network = kwargs.get('network', None) - self.plugins = kwargs.get('plugins', []) + if self.network: + self.num_blocks = self.network.get_local_height() + self.num_nodes = len(self.network.get_interfaces()) + host, port, protocol, proxy_config, auto_connect = self.network.get_parameters() + self.server_host = host + self.server_port = port + self.auto_connect = auto_connect + self.proxy_config = proxy_config if proxy_config else {} + self.plugins = kwargs.get('plugins', []) self.gui_object = kwargs.get('gui_object', None) self.daemon = self.gui_object.daemon self.fx = self.daemon.fx + self.use_rbf = config.get('use_rbf', False) + self.use_change = config.get('use_change', True) + self.use_unconfirmed = not config.get('confirmed_only', True) + # create triggers so as to minimize updation a max of 2 times a sec self._trigger_update_wallet = Clock.create_trigger(self.update_wallet, .5) self._trigger_update_status = Clock.create_trigger(self.update_status, .5) @@ -529,14 +587,31 @@ class ElectrumWindow(App): if self.network: interests = ['updated', 'status', 'new_transaction', 'verified'] self.network.register_callback(self.on_network, interests) - self.tabs = self.root.ids['tabs'] + def blockchain_status(self): + if len(self.network.blockchains)>1: + msg = self.network.get_blockchain_name(self.network.blockchain()) + else: + msg = _('Genesis block') + return msg + + def blockchain_info(self): + if len(self.network.blockchains)>1: + checkpoint = self.network.get_checkpoint() + msg = _('Chain split detected at block %d')%checkpoint + else: + msg = _('No chain split detected') + return msg + def on_network(self, event, *args): + if self.network.interface: + self.server_host = self.network.interface.host if event == 'updated': self.num_blocks = self.network.get_local_height() self.num_nodes = len(self.network.get_interfaces()) self._trigger_update_wallet() + self._trigger_update_status() elif event == 'status': self._trigger_update_status() elif event == 'new_transaction': @@ -573,9 +648,11 @@ class ElectrumWindow(App): text = self.format_amount(c+x+u) status = str(text.strip() + ' ' + self.base_unit) else: - status = _("Not connected") + status = _("Disconnected") + n = self.wallet.basename() self.status = '[size=15dp]%s[/size]\n%s' %(n, status) + #fiat_balance = self.fx.format_amount_and_units(c+u+x) or '' def get_max_amount(self): inputs = self.wallet.get_spendable_coins(None, self.electrum_config) diff --git a/gui/kivy/uix/dialogs/amount_dialog.py b/gui/kivy/uix/dialogs/amount_dialog.py @@ -79,7 +79,7 @@ Builder.load_string(''' id: button_fiat size_hint: 1, None height: '48dp' - text: (app.base_unit if kb.is_fiat else app.fiat_unit) if app.fiat_unit else '' + text: (app.base_unit if not kb.is_fiat else app.fiat_unit) if app.fiat_unit else '' on_release: if app.fiat_unit: popup.toggle_fiat(kb) Button: diff --git a/gui/kivy/uix/dialogs/blockchain_dialog.py b/gui/kivy/uix/dialogs/blockchain_dialog.py @@ -1,63 +0,0 @@ -from kivy.app import App -from kivy.factory import Factory -from kivy.properties import ObjectProperty -from kivy.lang import Builder - -from electrum.i18n import _ - -Builder.load_string(''' -#:import _ electrum_gui.kivy.i18n._ - -<BlockchainDialog@Popup> - id: popup - title: _('Blockchain') - size_hint: 1, 1 - cp_height: 0 - cp_value: '' - - BoxLayout: - orientation: 'vertical' - padding: '10dp' - spacing: '10dp' - TopLabel: - height: '48dp' - id: bc_height - text: _("Verified headers: %d blocks.")% app.num_blocks - TopLabel: - height: '48dp' - id: bc_status - text: _("Connected to %d nodes.")% app.num_nodes if app.num_nodes else _("Not connected?") - Widget: - size_hint: 1, 0.1 - TopLabel: - text: _("Electrum connects to several nodes in order to download block headers and find out the longest blockchain.") + _("This blockchain is used to verify the transactions sent by your transaction server.") - font_size: '6pt' - Widget: - size_hint: 1, 0.1 - Widget: - size_hint: 1, 0.1 - BoxLayout: - orientation: 'horizontal' - size_hint: 1, 0.2 - Button: - text: _('Cancel') - size_hint: 0.5, None - height: '48dp' - on_release: popup.dismiss() - Button: - text: _('OK') - size_hint: 0.5, None - height: '48dp' - on_release: - root.callback(root.cp_height, root.cp_value) - popup.dismiss() -''') - -class BlockchainDialog(Factory.Popup): - def __init__(self, network, callback): - Factory.Popup.__init__(self) - self.network = network - self.callback = callback - self.is_split = len(self.network.blockchains) > 1 - - self.checkpoint_height = network.get_checkpoint() diff --git a/gui/kivy/uix/dialogs/settings.py b/gui/kivy/uix/dialogs/settings.py @@ -16,35 +16,6 @@ Builder.load_string(''' #:import partial functools.partial #:import _ electrum_gui.kivy.i18n._ -<SettingsItem@ButtonBehavior+BoxLayout> - orientation: 'vertical' - title: '' - description: '' - size_hint: 1, None - height: '60dp' - - canvas.before: - Color: - rgba: (0.192, .498, 0.745, 1) if self.state == 'down' else (0.3, 0.3, 0.3, 0) - Rectangle: - size: self.size - pos: self.pos - on_release: - Clock.schedule_once(self.action) - - Widget - TopLabel: - id: title - text: self.parent.title - bold: True - halign: 'left' - TopLabel: - text: self.parent.description - color: 0.8, 0.8, 0.8, 1 - halign: 'left' - Widget - - <SettingsDialog@Popup> id: settings title: _('Electrum Settings') @@ -91,40 +62,40 @@ Builder.load_string(''' action: partial(root.fx_dialog, self) CardSeparator SettingsItem: - status: root.network_status() - title: _('Server') + ': ' + self.status - description: _("Select your history server.") - action: partial(root.network_dialog, self) - CardSeparator - SettingsItem: - status: root.proxy_status() - title: _('Proxy') + ': ' + self.status - description: _("Proxy configuration.") - action: partial(root.proxy_dialog, self) - CardSeparator - SettingsItem: status: 'ON' if bool(app.plugins.get('labels')) else 'OFF' title: _('Labels Sync') + ': ' + self.status description: _("Save and synchronize your labels.") action: partial(root.plugin_dialog, 'labels', self) CardSeparator SettingsItem: - status: root.rbf_status() + status: 'ON' if app.use_rbf else 'OFF' title: _('Replace-by-fee') + ': ' + self.status description: _("Create replaceable transactions.") - action: partial(root.rbf_dialog, self) + message: + _('If you check this box, your transactions will be marked as non-final,') \ + + ' ' + _('and you will have the possiblity, while they are unconfirmed, to replace them with transactions that pays higher fees.') \ + + ' ' + _('Note that some merchants do not accept non-final transactions until they are confirmed.') + action: partial(root.boolean_dialog, 'use_rbf', _('Replace by fee'), self.message) + CardSeparator + SettingsItem: + status: _('Yes') if app.use_unconfirmed else _('No') + title: _('Spend unconfirmed') + ': ' + self.status + description: _("Use unconfirmed coins in transactions.") + message: _('Spend unconfirmed coins') + action: partial(root.boolean_dialog, 'use_unconfirmed', _('Use unconfirmed'), self.message) + CardSeparator + SettingsItem: + status: _('Yes') if app.use_change else _('No') + title: _('Use change addresses') + ': ' + self.status + description: _("Send your change to separate addresses.") + message: _('Send excess coins to change addresses') + action: partial(root.boolean_dialog, 'use_change', _('Use change addresses'), self.message) CardSeparator SettingsItem: status: root.coinselect_status() title: _('Coin selection') + ': ' + self.status description: "Coin selection method" action: partial(root.coinselect_dialog, self) - CardSeparator - SettingsItem: - status: "%d blocks"% app.num_blocks - title: _('Blockchain') + ': ' + self.status - description: _("Blockchain status") - action: partial(root.blockchain_dialog, self) ''') @@ -141,13 +112,10 @@ class SettingsDialog(Factory.Popup): # cached dialogs self._fx_dialog = None self._fee_dialog = None - self._rbf_dialog = None - self._network_dialog = None self._proxy_dialog = None self._language_dialog = None self._unit_dialog = None self._coinselect_dialog = None - self._blockchain_dialog = None def update(self): self.wallet = self.app.wallet @@ -191,15 +159,6 @@ class SettingsDialog(Factory.Popup): self._coinselect_dialog = ChoiceDialog(_('Coin selection'), choosers, chooser_name, cb) self._coinselect_dialog.open() - def blockchain_dialog(self, item, dt): - from blockchain_dialog import BlockchainDialog - if self._blockchain_dialog is None: - def callback(height, value): - if value: - self.app.network.blockchain.set_checkpoint(height, value) - self._blockchain_dialog = BlockchainDialog(self.app.network, callback) - self._blockchain_dialog.open() - def proxy_status(self): server, port, protocol, proxy, auto_connect = self.app.network.get_parameters() return proxy.get('host') +':' + proxy.get('port') if proxy else _('None') @@ -230,44 +189,11 @@ class SettingsDialog(Factory.Popup): self._proxy_dialog = popup self._proxy_dialog.open() - def network_dialog(self, item, dt): - host, port, protocol, proxy, auto_connect = self.app.network.get_parameters() - servers = self.app.network.get_servers() - if self._network_dialog is None: - def cb1(popup): - host = str(popup.ids.host.text) - port = str(popup.ids.port.text) - auto_connect = popup.ids.auto_connect.active - self.app.network.set_parameters(host, port, protocol, proxy, auto_connect) - item.status = self.network_status() - def cb2(host): - from electrum.network import DEFAULT_PORTS - pp = servers.get(host, DEFAULT_PORTS) - port = pp.get(protocol, '') - popup.ids.host.text = host - popup.ids.port.text = port - def cb3(): - ChoiceDialog(_('Choose a server'), sorted(servers), popup.ids.host.text, cb2).open() - popup = Builder.load_file('gui/kivy/uix/ui_screens/network.kv') - popup.ids.chooser.on_release = cb3 - popup.on_dismiss = lambda: cb1(popup) - self._network_dialog = popup - - self._network_dialog.ids.auto_connect.active = auto_connect - self._network_dialog.ids.host.text = host - self._network_dialog.ids.port.text = port - self._network_dialog.open() - - def network_status(self): - server, port, protocol, proxy, auto_connect = self.app.network.get_parameters() - return 'auto-connect' if auto_connect else server - def plugin_dialog(self, name, label, dt): from checkbox_dialog import CheckBoxDialog def callback(status): self.plugins.enable(name) if status else self.plugins.disable(name) label.status = 'ON' if status else 'OFF' - status = bool(self.plugins.get(name)) dd = self.plugins.descriptions.get(name) descr = dd.get('description') @@ -289,21 +215,9 @@ class SettingsDialog(Factory.Popup): self._fee_dialog = FeeDialog(self.app, self.config, cb) self._fee_dialog.open() - def rbf_status(self): - return 'ON' if self.config.get('use_rbf') else 'OFF' - - def rbf_dialog(self, label, dt): - if self._rbf_dialog is None: - from checkbox_dialog import CheckBoxDialog - def cb(x): - self.config.set_key('use_rbf', x, True) - label.status = self.rbf_status() - msg = [_('If you check this box, your transactions will be marked as non-final,'), - _('and you will have the possiblity, while they are unconfirmed, to replace them with transactions that pays higher fees.'), - _('Note that some merchants do not accept non-final transactions until they are confirmed.')] - fullname = _('Replace by fee') - self._rbf_dialog = CheckBoxDialog(fullname, ' '.join(msg), self.config.get('use_rbf', False), cb) - self._rbf_dialog.open() + def boolean_dialog(self, name, title, message, dt): + from checkbox_dialog import CheckBoxDialog + CheckBoxDialog(title, message, getattr(self.app, name), lambda x: setattr(self.app, name, x)).open() def fx_status(self): fx = self.app.fx diff --git a/gui/kivy/uix/ui_screens/network.kv b/gui/kivy/uix/ui_screens/network.kv @@ -1,72 +1,53 @@ Popup: id: nd - title: _('Server') - is_connected: app.network.is_connected() - + title: _('Network') BoxLayout: orientation: 'vertical' - padding: '10dp' - spacing: '10dp' - TopLabel: - text: _("Electrum requests your transaction history from a single server. The returned history is then checked against blockchain headers sent by other nodes, using Simple Payment Verification (SPV).") - font_size: '6pt' - Widget: - size_hint: 1, 0.8 - GridLayout: - cols: 3 - Label: - height: '36dp' - size_hint_x: 1 - size_hint_y: None - text: _('Host') + ':' - TextInput: - id: host - multiline: False - height: '36dp' - size_hint_x: 3 - size_hint_y: None - text: '' - disabled: auto_connect.active - Button: - id: chooser - text:'v' - height: '36dp' - size_hint_x: 0.5 - size_hint_y: None - disabled: auto_connect.active - Label: - height: '36dp' - size_hint_x: 1 - size_hint_y: None - text: _('Port') + ':' - TextInput: - id: port - multiline: False - input_type: 'number' - height: '36dp' - size_hint_x: 3 - size_hint_y: None - text: '' - disabled: auto_connect.active - Widget: - size_hint: 1, 0.1 - TopLabel: - text: _("If auto-connect is checked, your history server will be selected automatically.") - font_size: '6pt' - BoxLayout: - Label: - text: _('Auto-connect') - CheckBox: - id: auto_connect - size_hint_y: None - Widget: - size_hint: 1, 0.1 - BoxLayout: - Widget: - size_hint: 0.5, None - Button: - size_hint: 0.5, None - height: '48dp' - text: _('OK') - on_release: - nd.dismiss() + ScrollView: + GridLayout: + id: scrollviewlayout + cols:1 + size_hint: 1, None + height: self.minimum_height + padding: '10dp' + SettingsItem: + value: _("%d connections.")% app.num_nodes if app.num_nodes else _("Not connected") + title: _("Status") + ': ' + self.value + description: _("Connections with Electrum servers") + action: lambda x: x + + CardSeparator + SettingsItem: + title: _("Server") + ': ' + app.server_host + description: _("Server used to request your history.") + action: lambda x: app.popup_dialog('server') + + CardSeparator + SettingsItem: + title: _("Auto-connect") + ': ' + ('ON' if app.auto_connect else 'OFF') + description: _("Find a server automatically") + action: app.toggle_auto_connect + + CardSeparator + SettingsItem: + value: "%d blocks" % app.num_blocks + title: _("Blockchain") + ': ' + self.value + description: _("Verified block headers.") + action: lambda x: x + + CardSeparator + SettingsItem: + title: _("Checkpoint") + ': ' + app.blockchain_status() + description: app.blockchain_info() + action: app.choose_blockchain_dialog + + CardSeparator + SettingsItem: + proxy: app.proxy_config.get('mode') + host: app.proxy_config.get('host') + port: app.proxy_config.get('port') + title: _("Proxy") + ': ' + ((self.host +':' + self.port) if self.proxy else _('None')) + description: _('Proxy configuration') + action: lambda x: app.popup_dialog('proxy') + + diff --git a/gui/kivy/uix/ui_screens/proxy.kv b/gui/kivy/uix/ui_screens/proxy.kv @@ -13,8 +13,8 @@ Popup: id: mode height: '48dp' size_hint_y: None - text: '' - values: ['None', 'socks4', 'socks5', 'http'] + text: app.proxy_config.get('mode', 'none') + values: ['none', 'socks4', 'socks5', 'http'] Label: text: _('Host') TextInput: @@ -22,8 +22,8 @@ Popup: multiline: False height: '48dp' size_hint_y: None - text: '' - disabled: mode.text == 'None' + text: app.proxy_config.get('host', '') + disabled: mode.text == 'none' Label: text: _('Port') TextInput: @@ -32,8 +32,8 @@ Popup: input_type: 'number' height: '48dp' size_hint_y: None - text: '' - disabled: mode.text == 'None' + text: app.proxy_config.get('port', '') + disabled: mode.text == 'none' Label: text: _('Username') TextInput: @@ -41,8 +41,8 @@ Popup: multiline: False height: '48dp' size_hint_y: None - text: '' - disabled: mode.text == 'None' + text: app.proxy_config.get('user', '') + disabled: mode.text == 'none' Label: text: _('Password') TextInput: @@ -51,8 +51,8 @@ Popup: password: True height: '48dp' size_hint_y: None - text: '' - disabled: mode.text == 'None' + text: app.proxy_config.get('password', '') + disabled: mode.text == 'none' Widget: size_hint: 1, 0.1 BoxLayout: @@ -63,4 +63,14 @@ Popup: height: '48dp' text: _('OK') on_release: + host, port, protocol, proxy, auto_connect = app.network.get_parameters() + proxy = {} + proxy['mode']=str(root.ids.mode.text).lower() + proxy['host']=str(root.ids.host.text) + proxy['port']=str(root.ids.port.text) + proxy['user']=str(root.ids.user.text) + proxy['password']=str(root.ids.password.text) + if proxy['mode']=='none': proxy = None + app.network.set_parameters(host, port, protocol, proxy, auto_connect) + app.proxy_config = proxy if proxy else {} nd.dismiss() diff --git a/gui/kivy/uix/ui_screens/server.kv b/gui/kivy/uix/ui_screens/server.kv @@ -0,0 +1,63 @@ +Popup: + id: nd + title: _('Server') + BoxLayout: + orientation: 'vertical' + padding: '10dp' + spacing: '10dp' + TopLabel: + text: _("Electrum requests your transaction history from a single server. The returned history is checked against blockchain headers sent by other nodes, using Simple Payment Verification (SPV).") + font_size: '6pt' + Widget: + size_hint: 1, 0.8 + GridLayout: + cols: 2 + Label: + height: '36dp' + size_hint_x: 1 + size_hint_y: None + text: _('Host') + ':' + TextInput: + id: host + multiline: False + height: '36dp' + size_hint_x: 3 + size_hint_y: None + text: app.server_host + Label: + height: '36dp' + size_hint_x: 1 + size_hint_y: None + text: _('Port') + ':' + TextInput: + id: port + multiline: False + input_type: 'number' + height: '36dp' + size_hint_x: 3 + size_hint_y: None + text: app.server_port + Widget + Button: + id: chooser + text: _('Choose from peers') + height: '36dp' + size_hint_x: 0.5 + size_hint_y: None + on_release: + app.choose_server_dialog(root) + Widget: + size_hint: 1, 0.1 + BoxLayout: + Widget: + size_hint: 0.5, None + Button: + size_hint: 0.5, None + height: '48dp' + text: _('OK') + on_release: + host, port, protocol, proxy, auto_connect = app.network.get_parameters() + host = str(root.ids.host.text) + port = str(root.ids.port.text) + app.network.set_parameters(host, port, protocol, proxy, auto_connect) + nd.dismiss() diff --git a/gui/qt/network_dialog.py b/gui/qt/network_dialog.py @@ -393,10 +393,6 @@ class NetworkChoiceLayout(object): def follow_branch(self, index): self.network.follow_chain(index) - server = self.network.interface.server - host, port, protocol, proxy, auto_connect = self.network.get_parameters() - host, port, protocol = server.split(':') - self.network.set_parameters(host, port, protocol, proxy, auto_connect) self.update() def follow_server(self, server): diff --git a/lib/network.py b/lib/network.py @@ -1040,6 +1040,13 @@ class Network(util.DaemonThread): else: raise BaseException('blockchain not found', index) + if self.interface: + server = self.interface.server + host, port, protocol, proxy, auto_connect = self.get_parameters() + host, port, protocol = server.split(':') + self.set_parameters(host, port, protocol, proxy, auto_connect) + + def get_local_height(self): return self.blockchain().height()