Browse Source

Allow user to enable lightning in the GUI. Make it a per-wallet setting.

dependabot/pip/contrib/deterministic-build/ecdsa-0.13.3
ThomasV 5 years ago
parent
commit
90ce9f195b
  1. 8
      electrum/commands.py
  2. 7
      electrum/gui/kivy/main_window.py
  3. 2
      electrum/gui/kivy/uix/ui_screens/receive.kv
  4. 1
      electrum/gui/qt/__init__.py
  5. 9
      electrum/gui/qt/channels_list.py
  6. 40
      electrum/gui/qt/main_window.py
  7. 11
      electrum/gui/qt/settings_dialog.py
  8. 14
      electrum/lnworker.py
  9. 21
      electrum/network.py
  10. 15
      electrum/tests/regtest/regtest.sh
  11. 26
      electrum/wallet.py
  12. 8
      run_electrum

8
electrum/commands.py

@ -622,6 +622,12 @@ class Commands:
kwargs['fx'] = fx kwargs['fx'] = fx
return json_encode(wallet.get_detailed_history(**kwargs)) return json_encode(wallet.get_detailed_history(**kwargs))
@command('w')
async def init_lightning(self, wallet: Abstract_Wallet = None):
"""Enable lightning payments"""
wallet.init_lightning()
return "Lightning keys have been created."
@command('w') @command('w')
async def lightning_history(self, show_fiat=False, wallet: Abstract_Wallet = None): async def lightning_history(self, show_fiat=False, wallet: Abstract_Wallet = None):
""" lightning history """ """ lightning history """
@ -1127,8 +1133,6 @@ def add_global_options(parser):
group.add_argument("--testnet", action="store_true", dest="testnet", default=False, help="Use Testnet") group.add_argument("--testnet", action="store_true", dest="testnet", default=False, help="Use Testnet")
group.add_argument("--regtest", action="store_true", dest="regtest", default=False, help="Use Regtest") group.add_argument("--regtest", action="store_true", dest="regtest", default=False, help="Use Regtest")
group.add_argument("--simnet", action="store_true", dest="simnet", default=False, help="Use Simnet") group.add_argument("--simnet", action="store_true", dest="simnet", default=False, help="Use Simnet")
group.add_argument("--lightning", action="store_true", dest="lightning", default=False, help="Enable lightning")
group.add_argument("--reckless", action="store_true", dest="reckless", default=False, help="Allow to enable lightning on mainnet")
group.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run offline") group.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run offline")
def add_wallet_option(parser): def add_wallet_option(parser):

7
electrum/gui/kivy/main_window.py

@ -341,9 +341,6 @@ class ElectrumWindow(App):
self.gui_object = kwargs.get('gui_object', None) # type: ElectrumGui self.gui_object = kwargs.get('gui_object', None) # type: ElectrumGui
self.daemon = self.gui_object.daemon self.daemon = self.gui_object.daemon
self.fx = self.daemon.fx self.fx = self.daemon.fx
self.is_lightning_enabled = bool(config.get('lightning'))
self.use_rbf = config.get('use_rbf', True) self.use_rbf = config.get('use_rbf', True)
self.use_unconfirmed = not config.get('confirmed_only', False) self.use_unconfirmed = not config.get('confirmed_only', False)
@ -558,7 +555,7 @@ class ElectrumWindow(App):
self.network.register_callback(self.on_fee_histogram, ['fee_histogram']) self.network.register_callback(self.on_fee_histogram, ['fee_histogram'])
self.network.register_callback(self.on_quotes, ['on_quotes']) self.network.register_callback(self.on_quotes, ['on_quotes'])
self.network.register_callback(self.on_history, ['on_history']) self.network.register_callback(self.on_history, ['on_history'])
self.network.register_callback(self.on_channels, ['channels']) self.network.register_callback(self.on_channels, ['channels_updated'])
self.network.register_callback(self.on_channel, ['channel']) self.network.register_callback(self.on_channel, ['channel'])
self.network.register_callback(self.on_invoice_status, ['invoice_status']) self.network.register_callback(self.on_invoice_status, ['invoice_status'])
self.network.register_callback(self.on_request_status, ['request_status']) self.network.register_callback(self.on_request_status, ['request_status'])
@ -687,7 +684,7 @@ class ElectrumWindow(App):
if self._channels_dialog: if self._channels_dialog:
Clock.schedule_once(lambda dt: self._channels_dialog.update()) Clock.schedule_once(lambda dt: self._channels_dialog.update())
def on_channels(self, evt): def on_channels(self, evt, wallet):
if self._channels_dialog: if self._channels_dialog:
Clock.schedule_once(lambda dt: self._channels_dialog.update()) Clock.schedule_once(lambda dt: self._channels_dialog.update())

2
electrum/gui/kivy/uix/ui_screens/receive.kv

@ -91,7 +91,7 @@ ReceiveScreen:
id: address_label id: address_label
text: _('Lightning') if root.is_lightning else (s.address if s.address else _('Bitcoin Address')) text: _('Lightning') if root.is_lightning else (s.address if s.address else _('Bitcoin Address'))
shorten: True shorten: True
on_release: root.is_lightning = not root.is_lightning if app.is_lightning_enabled else False on_release: root.is_lightning = not root.is_lightning if app.wallet.has_lightning() else False
CardSeparator: CardSeparator:
opacity: message_selection.opacity opacity: message_selection.opacity
color: blue_bottom.foreground_color color: blue_bottom.foreground_color

1
electrum/gui/qt/__init__.py

@ -155,7 +155,6 @@ class ElectrumGui(Logger):
else: else:
m = self.tray.contextMenu() m = self.tray.contextMenu()
m.clear() m.clear()
if self.config.get('lightning'):
m.addAction(_("Lightning"), self.show_lightning_dialog) m.addAction(_("Lightning"), self.show_lightning_dialog)
m.addAction(_("Watchtower"), self.show_watchtower_dialog) m.addAction(_("Watchtower"), self.show_watchtower_dialog)
for window in self.windows: for window in self.windows:

9
electrum/gui/qt/channels_list.py

@ -10,6 +10,7 @@ from PyQt5.QtWidgets import QMenu, QHBoxLayout, QLabel, QVBoxLayout, QGridLayout
from electrum.util import inv_dict, bh2u, bfh from electrum.util import inv_dict, bh2u, bfh
from electrum.i18n import _ from electrum.i18n import _
from electrum.lnchannel import Channel from electrum.lnchannel import Channel
from electrum.wallet import Abstract_Wallet
from electrum.lnutil import LOCAL, REMOTE, ConnStringFormatError, format_short_channel_id from electrum.lnutil import LOCAL, REMOTE, ConnStringFormatError, format_short_channel_id
from .util import MyTreeView, WindowModalDialog, Buttons, OkButton, CancelButton, EnterButton, WWLabel, WaitingDialog from .util import MyTreeView, WindowModalDialog, Buttons, OkButton, CancelButton, EnterButton, WWLabel, WaitingDialog
@ -21,7 +22,7 @@ ROLE_CHANNEL_ID = Qt.UserRole
class ChannelsList(MyTreeView): class ChannelsList(MyTreeView):
update_rows = QtCore.pyqtSignal() update_rows = QtCore.pyqtSignal(Abstract_Wallet)
update_single_row = QtCore.pyqtSignal(Channel) update_single_row = QtCore.pyqtSignal(Channel)
class Columns(IntEnum): class Columns(IntEnum):
@ -121,8 +122,10 @@ class ChannelsList(MyTreeView):
for column, v in enumerate(self.format_fields(chan)): for column, v in enumerate(self.format_fields(chan)):
self.model().item(row, column).setData(v, QtCore.Qt.DisplayRole) self.model().item(row, column).setData(v, QtCore.Qt.DisplayRole)
@QtCore.pyqtSlot() @QtCore.pyqtSlot(Abstract_Wallet)
def do_update_rows(self): def do_update_rows(self, wallet):
if wallet != self.parent.wallet:
return
self.model().clear() self.model().clear()
self.update_headers(self.headers) self.update_headers(self.headers)
for chan in self.parent.wallet.lnworker.channels.values(): for chan in self.parent.wallet.lnworker.channels.values():

40
electrum/gui/qt/main_window.py

@ -196,7 +196,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
tabs.addTab(tab, icon, description.replace("&", "")) tabs.addTab(tab, icon, description.replace("&", ""))
add_optional_tab(tabs, self.addresses_tab, read_QIcon("tab_addresses.png"), _("&Addresses"), "addresses") add_optional_tab(tabs, self.addresses_tab, read_QIcon("tab_addresses.png"), _("&Addresses"), "addresses")
if self.config.get('lightning'): if self.wallet.has_lightning():
add_optional_tab(tabs, self.channels_tab, read_QIcon("lightning.png"), _("Channels"), "channels") add_optional_tab(tabs, self.channels_tab, read_QIcon("lightning.png"), _("Channels"), "channels")
add_optional_tab(tabs, self.utxo_tab, read_QIcon("tab_coins.png"), _("Co&ins"), "utxo") add_optional_tab(tabs, self.utxo_tab, read_QIcon("tab_coins.png"), _("Co&ins"), "utxo")
add_optional_tab(tabs, self.contacts_tab, read_QIcon("tab_contacts.png"), _("Con&tacts"), "contacts") add_optional_tab(tabs, self.contacts_tab, read_QIcon("tab_contacts.png"), _("Con&tacts"), "contacts")
@ -232,7 +232,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
interests = ['wallet_updated', 'network_updated', 'blockchain_updated', interests = ['wallet_updated', 'network_updated', 'blockchain_updated',
'new_transaction', 'status', 'new_transaction', 'status',
'banner', 'verified', 'fee', 'fee_histogram', 'on_quotes', 'banner', 'verified', 'fee', 'fee_histogram', 'on_quotes',
'on_history', 'channel', 'channels', 'on_history', 'channel', 'channels_updated',
'invoice_status', 'request_status'] 'invoice_status', 'request_status']
# To avoid leaking references to "self" that prevent the # To avoid leaking references to "self" that prevent the
# window from being GC-ed when closed, callbacks should be # window from being GC-ed when closed, callbacks should be
@ -377,8 +377,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
self.on_fx_quotes() self.on_fx_quotes()
elif event == 'on_history': elif event == 'on_history':
self.on_fx_history() self.on_fx_history()
elif event == 'channels': elif event == 'channels_updated':
self.channels_list.update_rows.emit() self.channels_list.update_rows.emit(*args)
elif event == 'channel': elif event == 'channel':
self.channels_list.update_single_row.emit(*args) self.channels_list.update_single_row.emit(*args)
self.update_status() self.update_status()
@ -583,7 +583,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
file_menu.addAction(_("&Quit"), self.close) file_menu.addAction(_("&Quit"), self.close)
wallet_menu = menubar.addMenu(_("&Wallet")) wallet_menu = menubar.addMenu(_("&Wallet"))
wallet_menu.addAction(_("&Information"), self.show_master_public_keys) wallet_menu.addAction(_("&Information"), self.show_wallet_info)
wallet_menu.addSeparator() wallet_menu.addSeparator()
self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog) self.password_menu = wallet_menu.addAction(_("&Password"), self.change_password_dialog)
self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog) self.seed_menu = wallet_menu.addAction(_("&Seed"), self.show_seed_dialog)
@ -623,7 +623,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
view_menu = menubar.addMenu(_("&View")) view_menu = menubar.addMenu(_("&View"))
add_toggle_action(view_menu, self.addresses_tab) add_toggle_action(view_menu, self.addresses_tab)
add_toggle_action(view_menu, self.utxo_tab) add_toggle_action(view_menu, self.utxo_tab)
if self.config.get('lightning'): if self.wallet.has_lightning():
add_toggle_action(view_menu, self.channels_tab) add_toggle_action(view_menu, self.channels_tab)
add_toggle_action(view_menu, self.contacts_tab) add_toggle_action(view_menu, self.contacts_tab)
add_toggle_action(view_menu, self.console_tab) add_toggle_action(view_menu, self.console_tab)
@ -633,7 +633,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
# Settings / Preferences are all reserved keywords in macOS using this as work around # Settings / Preferences are all reserved keywords in macOS using this as work around
tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog) tools_menu.addAction(_("Electrum preferences") if sys.platform == 'darwin' else _("Preferences"), self.settings_dialog)
tools_menu.addAction(_("&Network"), lambda: self.gui_object.show_network_dialog(self)) tools_menu.addAction(_("&Network"), lambda: self.gui_object.show_network_dialog(self))
if self.config.get('lightning'): if self.wallet.has_lightning():
tools_menu.addAction(_("&Lightning"), self.gui_object.show_lightning_dialog) tools_menu.addAction(_("&Lightning"), self.gui_object.show_lightning_dialog)
tools_menu.addAction(_("&Watchtower"), self.gui_object.show_watchtower_dialog) tools_menu.addAction(_("&Watchtower"), self.gui_object.show_watchtower_dialog)
tools_menu.addAction(_("&Plugins"), self.plugins_dialog) tools_menu.addAction(_("&Plugins"), self.plugins_dialog)
@ -985,7 +985,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
buttons.addStretch(1) buttons.addStretch(1)
buttons.addWidget(self.clear_invoice_button) buttons.addWidget(self.clear_invoice_button)
buttons.addWidget(self.create_invoice_button) buttons.addWidget(self.create_invoice_button)
if self.config.get('lightning'): if self.wallet.has_lightning():
self.create_lightning_invoice_button = QPushButton(_('Lightning')) self.create_lightning_invoice_button = QPushButton(_('Lightning'))
self.create_lightning_invoice_button.setIcon(read_QIcon("lightning.png")) self.create_lightning_invoice_button.setIcon(read_QIcon("lightning.png"))
self.create_lightning_invoice_button.clicked.connect(lambda: self.create_invoice(True)) self.create_lightning_invoice_button.clicked.connect(lambda: self.create_invoice(True))
@ -2300,7 +2300,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
sb.addPermanentWidget(StatusBarButton(read_QIcon("preferences.png"), _("Preferences"), self.settings_dialog ) ) sb.addPermanentWidget(StatusBarButton(read_QIcon("preferences.png"), _("Preferences"), self.settings_dialog ) )
self.seed_button = StatusBarButton(read_QIcon("seed.png"), _("Seed"), self.show_seed_dialog ) self.seed_button = StatusBarButton(read_QIcon("seed.png"), _("Seed"), self.show_seed_dialog )
sb.addPermanentWidget(self.seed_button) sb.addPermanentWidget(self.seed_button)
if self.config.get('lightning'): if self.wallet.has_lightning():
self.lightning_button = StatusBarButton(read_QIcon("lightning.png"), _("Lightning Network"), self.gui_object.show_lightning_dialog) self.lightning_button = StatusBarButton(read_QIcon("lightning.png"), _("Lightning Network"), self.gui_object.show_lightning_dialog)
sb.addPermanentWidget(self.lightning_button) sb.addPermanentWidget(self.lightning_button)
self.status_button = StatusBarButton(read_QIcon("status_disconnected.png"), _("Network"), lambda: self.gui_object.show_network_dialog(self)) self.status_button = StatusBarButton(read_QIcon("status_disconnected.png"), _("Network"), lambda: self.gui_object.show_network_dialog(self))
@ -2390,7 +2390,16 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
if d.exec_(): if d.exec_():
self.set_contact(line2.text(), line1.text()) self.set_contact(line2.text(), line1.text())
def show_master_public_keys(self): def enable_lightning(self):
warning1 = _("Lightning support in Electrum is experimental. Do not put large amounts in lightning channels.")
warning2 = _("Funds stored in lightning channels are not recoverable from your seed. You must backup your wallet file everytime you crate a new channel.")
r = self.question(_('Enable Lightning payments?') + '\n\n' + _('WARNINGS') + ': ' + '\n\n' + warning1 + '\n\n' + warning2)
if not r:
return
self.wallet.init_lightning()
self.show_warning(_('Lightning keys have been initialized. Please restart Electrum'))
def show_wallet_info(self):
dialog = WindowModalDialog(self, _("Wallet Information")) dialog = WindowModalDialog(self, _("Wallet Information"))
dialog.setMinimumSize(500, 100) dialog.setMinimumSize(500, 100)
mpk_list = self.wallet.get_master_public_keys() mpk_list = self.wallet.get_master_public_keys()
@ -2414,6 +2423,17 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
grid.addWidget(QLabel(_("Keystore type") + ':'), 4, 0) grid.addWidget(QLabel(_("Keystore type") + ':'), 4, 0)
ks_type = str(keystore_types[0]) if keystore_types else _('No keystore') ks_type = str(keystore_types[0]) if keystore_types else _('No keystore')
grid.addWidget(QLabel(ks_type), 4, 1) grid.addWidget(QLabel(ks_type), 4, 1)
# lightning
if self.wallet.has_lightning():
lightning_b = None
lightning_label = QLabel(_('Enabled'))
else:
lightning_b = QPushButton(_('Enable'))
lightning_b.clicked.connect(self.enable_lightning)
lightning_label = QLabel(_('Disabled'))
grid.addWidget(QLabel(_('Lightning')), 5, 0)
grid.addWidget(lightning_label, 5, 1)
grid.addWidget(lightning_b, 5, 2)
vbox.addLayout(grid) vbox.addLayout(grid)
if self.wallet.is_deterministic(): if self.wallet.is_deterministic():

11
electrum/gui/qt/settings_dialog.py

@ -171,18 +171,7 @@ class SettingsDialog(WindowModalDialog):
fee_widgets.append((batch_rbf_cb, None)) fee_widgets.append((batch_rbf_cb, None))
# lightning # lightning
help_lightning = _("""Enable Lightning Network payments. Note that funds stored in
lightning channels are not recoverable from your seed. You must backup
your wallet file after every channel creation.""")
lightning_widgets = [] lightning_widgets = []
lightning_cb = QCheckBox(_("Enable Lightning"))
lightning_cb.setToolTip(help_lightning)
lightning_cb.setChecked(bool(self.config.get('lightning', False)))
def on_lightning_checked(x):
self.config.set_key('lightning', bool(x))
lightning_cb.stateChanged.connect(on_lightning_checked)
lightning_widgets.append((lightning_cb, None))
help_persist = _("""If this option is checked, Electrum will persist as a daemon after help_persist = _("""If this option is checked, Electrum will persist as a daemon after
you close all your wallet windows. Your local watchtower will keep you close all your wallet windows. Your local watchtower will keep
running, and it will protect your channels even if your wallet is not running, and it will protect your channels even if your wallet is not

14
electrum/lnworker.py

@ -307,19 +307,11 @@ class LNGossip(LNWorker):
class LNWallet(LNWorker): class LNWallet(LNWorker):
def __init__(self, wallet: 'Abstract_Wallet'): def __init__(self, wallet: 'Abstract_Wallet', xprv):
Logger.__init__(self) Logger.__init__(self)
self.wallet = wallet self.wallet = wallet
self.storage = wallet.storage self.storage = wallet.storage
self.config = wallet.config self.config = wallet.config
xprv = self.storage.get('lightning_privkey2')
if xprv is None:
# TODO derive this deterministically from wallet.keystore at keystore generation time
# probably along a hardened path ( lnd-equivalent would be m/1017'/coinType'/ )
seed = os.urandom(32)
node = BIP32Node.from_rootseed(seed, xtype='standard')
xprv = node.to_xprv()
self.storage.put('lightning_privkey2', xprv)
LNWorker.__init__(self, xprv) LNWorker.__init__(self, xprv)
self.ln_keystore = keystore.from_xprv(xprv) self.ln_keystore = keystore.from_xprv(xprv)
self.localfeatures |= LnLocalFeatures.OPTION_DATA_LOSS_PROTECT_REQ self.localfeatures |= LnLocalFeatures.OPTION_DATA_LOSS_PROTECT_REQ
@ -789,7 +781,7 @@ class LNWallet(LNWorker):
return chan return chan
def on_channels_updated(self): def on_channels_updated(self):
self.network.trigger_callback('channels') self.network.trigger_callback('channels_updated', self.wallet)
@log_exceptions @log_exceptions
async def add_peer(self, connect_str: str) -> Peer: async def add_peer(self, connect_str: str) -> Peer:
@ -1211,7 +1203,7 @@ class LNWallet(LNWorker):
with self.lock: with self.lock:
self.channels.pop(chan_id) self.channels.pop(chan_id)
self.save_channels() self.save_channels()
self.network.trigger_callback('channels', self.wallet) self.network.trigger_callback('channels_updated', self.wallet)
self.network.trigger_callback('wallet_updated', self.wallet) self.network.trigger_callback('wallet_updated', self.wallet)
async def reestablish_peer_for_given_channel(self, chan): async def reestablish_peer_for_given_channel(self, chan):

21
electrum/network.py

@ -302,7 +302,12 @@ class Network(Logger):
self._set_status('disconnected') self._set_status('disconnected')
# lightning network # lightning network
if self.config.get('lightning'): self.channel_db = None # type: Optional[ChannelDB]
self.lngossip = None # type: Optional[LNGossip]
self.local_watchtower = None # type: Optional[WatchTower]
def maybe_init_lightning(self):
if self.channel_db is None:
from . import lnwatcher from . import lnwatcher
from . import lnworker from . import lnworker
from . import lnrouter from . import lnrouter
@ -311,10 +316,10 @@ class Network(Logger):
self.path_finder = lnrouter.LNPathFinder(self.channel_db) self.path_finder = lnrouter.LNPathFinder(self.channel_db)
self.lngossip = lnworker.LNGossip(self) self.lngossip = lnworker.LNGossip(self)
self.local_watchtower = lnwatcher.WatchTower(self) if self.config.get('local_watchtower', False) else None self.local_watchtower = lnwatcher.WatchTower(self) if self.config.get('local_watchtower', False) else None
else: self.lngossip.start_network(self)
self.channel_db = None # type: Optional[ChannelDB] if self.local_watchtower:
self.lngossip = None # type: Optional[LNGossip] self.local_watchtower.start_network(self)
self.local_watchtower = None # type: Optional[WatchTower] asyncio.ensure_future(self.local_watchtower.start_watching)
def run_from_another_thread(self, coro, *, timeout=None): def run_from_another_thread(self, coro, *, timeout=None):
assert self._loop_thread != threading.current_thread(), 'must not be called from network thread' assert self._loop_thread != threading.current_thread(), 'must not be called from network thread'
@ -1158,12 +1163,6 @@ class Network(Logger):
self._set_oneserver(self.config.get('oneserver', False)) self._set_oneserver(self.config.get('oneserver', False))
self._start_interface(self.default_server) self._start_interface(self.default_server)
if self.lngossip:
self.lngossip.start_network(self)
if self.local_watchtower:
self.local_watchtower.start_network(self)
await self.local_watchtower.start_watching()
async def main(): async def main():
try: try:
await self._init_headers_file() await self._init_headers_file()

15
electrum/tests/regtest/regtest.sh

@ -4,9 +4,9 @@ set -eu
# alice -> bob -> carol # alice -> bob -> carol
alice="./run_electrum --regtest --lightning -D /tmp/alice" alice="./run_electrum --regtest -D /tmp/alice"
bob="./run_electrum --regtest --lightning -D /tmp/bob" bob="./run_electrum --regtest -D /tmp/bob"
carol="./run_electrum --regtest --lightning -D /tmp/carol" carol="./run_electrum --regtest -D /tmp/carol"
bitcoin_cli="bitcoin-cli -rpcuser=doggman -rpcpassword=donkey -rpcport=18554 -regtest" bitcoin_cli="bitcoin-cli -rpcuser=doggman -rpcpassword=donkey -rpcport=18554 -regtest"
@ -18,7 +18,7 @@ function new_blocks()
function wait_for_balance() function wait_for_balance()
{ {
msg="wait until $1's balance reaches $2" msg="wait until $1's balance reaches $2"
cmd="./run_electrum --regtest --lightning -D /tmp/$1" cmd="./run_electrum --regtest -D /tmp/$1"
while balance=$($cmd getbalance | jq '[.confirmed, .unconfirmed] | to_entries | map(select(.value != null).value) | map(tonumber) | add ') && (( $(echo "$balance < $2" | bc -l) )); do while balance=$($cmd getbalance | jq '[.confirmed, .unconfirmed] | to_entries | map(select(.value != null).value) | map(tonumber) | add ') && (( $(echo "$balance < $2" | bc -l) )); do
sleep 1 sleep 1
msg="$msg." msg="$msg."
@ -30,7 +30,7 @@ function wait_for_balance()
function wait_until_channel_open() function wait_until_channel_open()
{ {
msg="wait until $1 sees channel open" msg="wait until $1 sees channel open"
cmd="./run_electrum --regtest --lightning -D /tmp/$1" cmd="./run_electrum --regtest -D /tmp/$1"
while channel_state=$($cmd list_channels | jq '.[0] | .state' | tr -d '"') && [ $channel_state != "OPEN" ]; do while channel_state=$($cmd list_channels | jq '.[0] | .state' | tr -d '"') && [ $channel_state != "OPEN" ]; do
sleep 1 sleep 1
msg="$msg." msg="$msg."
@ -42,7 +42,7 @@ function wait_until_channel_open()
function wait_until_channel_closed() function wait_until_channel_closed()
{ {
msg="wait until $1 sees channel closed" msg="wait until $1 sees channel closed"
cmd="./run_electrum --regtest --lightning -D /tmp/$1" cmd="./run_electrum --regtest -D /tmp/$1"
while [[ $($cmd list_channels | jq '.[0].state' | tr -d '"') != "CLOSED" ]]; do while [[ $($cmd list_channels | jq '.[0].state' | tr -d '"') != "CLOSED" ]]; do
sleep 1 sleep 1
msg="$msg." msg="$msg."
@ -73,6 +73,9 @@ if [[ $1 == "init" ]]; then
$alice create --offline > /dev/null $alice create --offline > /dev/null
$bob create --offline > /dev/null $bob create --offline > /dev/null
$carol create --offline > /dev/null $carol create --offline > /dev/null
$alice -o init_lightning
$bob -o init_lightning
$carol -o init_lightning
$alice setconfig --offline log_to_file True $alice setconfig --offline log_to_file True
$bob setconfig --offline log_to_file True $bob setconfig --offline log_to_file True
$carol setconfig --offline log_to_file True $carol setconfig --offline log_to_file True

26
electrum/wallet.py

@ -41,6 +41,7 @@ from decimal import Decimal
from typing import TYPE_CHECKING, List, Optional, Tuple, Union, NamedTuple, Sequence from typing import TYPE_CHECKING, List, Optional, Tuple, Union, NamedTuple, Sequence
from .i18n import _ from .i18n import _
from .bip32 import BIP32Node
from .crypto import sha256 from .crypto import sha256
from .util import (NotEnoughFunds, UserCancelled, profiler, from .util import (NotEnoughFunds, UserCancelled, profiler,
format_satoshis, format_fee_satoshis, NoDynamicFeeEstimates, format_satoshis, format_fee_satoshis, NoDynamicFeeEstimates,
@ -229,22 +230,36 @@ class Abstract_Wallet(AddressSynchronizer):
self.fiat_value = storage.get('fiat_value', {}) self.fiat_value = storage.get('fiat_value', {})
self.receive_requests = storage.get('payment_requests', {}) self.receive_requests = storage.get('payment_requests', {})
self.invoices = storage.get('invoices', {}) self.invoices = storage.get('invoices', {})
# convert invoices # convert invoices
for invoice_key, invoice in self.invoices.items(): for invoice_key, invoice in self.invoices.items():
if invoice.get('type') == PR_TYPE_ONCHAIN: if invoice.get('type') == PR_TYPE_ONCHAIN:
outputs = [TxOutput(*output) for output in invoice.get('outputs')] outputs = [TxOutput(*output) for output in invoice.get('outputs')]
invoice['outputs'] = outputs invoice['outputs'] = outputs
self.calc_unused_change_addresses() self.calc_unused_change_addresses()
# save wallet type the first time # save wallet type the first time
if self.storage.get('wallet_type') is None: if self.storage.get('wallet_type') is None:
self.storage.put('wallet_type', self.wallet_type) self.storage.put('wallet_type', self.wallet_type)
self.contacts = Contacts(self.storage) self.contacts = Contacts(self.storage)
self._coin_price_cache = {} self._coin_price_cache = {}
self.lnworker = LNWallet(self) if self.config.get('lightning') else None # lightning
ln_xprv = self.storage.get('lightning_privkey2')
self.lnworker = LNWallet(self, ln_xprv) if ln_xprv else None
def has_lightning(self):
return bool(self.lnworker)
def init_lightning(self):
if self.storage.get('lightning_privkey2'):
return
if not is_using_fast_ecc():
raise Exception('libsecp256k1 library not available. '
'Verifying Lightning channels is too computationally expensive without libsecp256k1, aborting.')
# TODO derive this deterministically from wallet.keystore at keystore generation time
# probably along a hardened path ( lnd-equivalent would be m/1017'/coinType'/ )
seed = os.urandom(32)
node = BIP32Node.from_rootseed(seed, xtype='standard')
ln_xprv = node.to_xprv()
self.storage.put('lightning_privkey2', ln_xprv)
def stop_threads(self): def stop_threads(self):
super().stop_threads() super().stop_threads()
@ -261,6 +276,7 @@ class Abstract_Wallet(AddressSynchronizer):
def start_network(self, network): def start_network(self, network):
AddressSynchronizer.start_network(self, network) AddressSynchronizer.start_network(self, network)
if self.lnworker: if self.lnworker:
network.maybe_init_lightning()
self.lnworker.start_network(network) self.lnworker.start_network(network)
def load_and_cleanup(self): def load_and_cleanup(self):

8
run_electrum

@ -333,14 +333,6 @@ if __name__ == '__main__':
constants.set_regtest() constants.set_regtest()
elif config.get('simnet'): elif config.get('simnet'):
constants.set_simnet() constants.set_simnet()
elif config.get('lightning') and not config.get('reckless'):
raise Exception('lightning option not available on mainnet')
if config.get('lightning'):
from electrum.ecc_fast import is_using_fast_ecc
if not is_using_fast_ecc():
raise Exception('libsecp256k1 library not available. '
'Verifying Lightning channels is too computationally expensive without libsecp256k1, aborting.')
cmdname = config.get('cmd') cmdname = config.get('cmd')

Loading…
Cancel
Save