Browse Source

Merge pull request #8454 from SomberNight/202305_configvar

config: introduce ConfigVars (take 3)
master
ghost43 3 years ago committed by GitHub
parent
commit
b6cfc12962
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      electrum/address_synchronizer.py
  2. 1
      electrum/base_crash_reporter.py
  3. 4
      electrum/base_wizard.py
  4. 15
      electrum/coinchooser.py
  5. 47
      electrum/commands.py
  6. 2
      electrum/contacts.py
  7. 42
      electrum/daemon.py
  8. 27
      electrum/exchange_rate.py
  9. 34
      electrum/gui/kivy/main_window.py
  10. 12
      electrum/gui/kivy/uix/dialogs/crash_reporter.py
  11. 10
      electrum/gui/kivy/uix/dialogs/fee_dialog.py
  12. 8
      electrum/gui/kivy/uix/dialogs/settings.py
  13. 4
      electrum/gui/kivy/uix/screens.py
  14. 4
      electrum/gui/qml/qeapp.py
  15. 5
      electrum/gui/qml/qechannelopener.py
  16. 82
      electrum/gui/qml/qeconfig.py
  17. 4
      electrum/gui/qml/qedaemon.py
  18. 6
      electrum/gui/qml/qefx.py
  19. 2
      electrum/gui/qml/qeinvoice.py
  20. 10
      electrum/gui/qml/qetxfinalizer.py
  21. 10
      electrum/gui/qt/__init__.py
  22. 8
      electrum/gui/qt/address_list.py
  23. 52
      electrum/gui/qt/confirm_tx_dialog.py
  24. 4
      electrum/gui/qt/exception_window.py
  25. 4
      electrum/gui/qt/fee_slider.py
  26. 12
      electrum/gui/qt/history_list.py
  27. 4
      electrum/gui/qt/installwizard.py
  28. 66
      electrum/gui/qt/main_window.py
  29. 28
      electrum/gui/qt/my_treeview.py
  30. 10
      electrum/gui/qt/network_dialog.py
  31. 2
      electrum/gui/qt/new_channel_dialog.py
  32. 4
      electrum/gui/qt/qrreader/qtmultimedia/camera_dialog.py
  33. 26
      electrum/gui/qt/receive_tab.py
  34. 75
      electrum/gui/qt/settings_dialog.py
  35. 8
      electrum/gui/qt/swap_dialog.py
  36. 4
      electrum/gui/qt/transaction_dialog.py
  37. 10
      electrum/gui/qt/util.py
  38. 6
      electrum/gui/text.py
  39. 8
      electrum/interface.py
  40. 26
      electrum/lnpeer.py
  41. 12
      electrum/lnworker.py
  42. 4
      electrum/logging.py
  43. 38
      electrum/network.py
  44. 10
      electrum/paymentrequest.py
  45. 1
      electrum/plugins/ledger/ledger.py
  46. 12
      electrum/plugins/payserver/payserver.py
  47. 14
      electrum/plugins/payserver/qt.py
  48. 4
      electrum/plugins/trustedcoin/qt.py
  49. 12
      electrum/plugins/trustedcoin/trustedcoin.py
  50. 331
      electrum/simple_config.py
  51. 2
      electrum/submarine_swaps.py
  52. 4
      electrum/tests/test_daemon.py
  53. 42
      electrum/tests/test_lnpeer.py
  54. 73
      electrum/tests/test_simple_config.py
  55. 4
      electrum/tests/test_sswaps.py
  56. 10
      electrum/tests/test_wallet_vertical.py
  57. 11
      electrum/util.py
  58. 2
      electrum/verifier.py
  59. 8
      electrum/wallet.py
  60. 15
      run_electrum

2
electrum/address_synchronizer.py

@ -959,7 +959,7 @@ class AddressSynchronizer(Logger, EventListener):
"""
max_conf = -1
h = self.db.get_addr_history(address)
needs_spv_check = not self.config.get("skipmerklecheck", False)
needs_spv_check = not self.config.NETWORK_SKIPMERKLECHECK
for tx_hash, tx_height in h:
if needs_spv_check:
tx_age = self.get_tx_height(tx_hash).conf

1
electrum/base_crash_reporter.py

@ -42,7 +42,6 @@ class CrashReportResponse(NamedTuple):
class BaseCrashReporter(Logger):
report_server = "https://crashhub.electrum.org"
config_key = "show_crash_reporter"
issue_template = """<h2>Traceback</h2>
<pre>
{traceback}

4
electrum/base_wizard.py

@ -92,7 +92,7 @@ class BaseWizard(Logger):
self._stack = [] # type: List[WizardStackItem]
self.plugin = None # type: Optional[BasePlugin]
self.keystores = [] # type: List[KeyStore]
self.is_kivy = config.get('gui') == 'kivy'
self.is_kivy = config.GUI_NAME == 'kivy'
self.seed_type = None
def set_icon(self, icon):
@ -697,7 +697,7 @@ class BaseWizard(Logger):
self.show_xpub_dialog(xpub=xpub, run_next=lambda x: self.run('choose_keystore'))
def choose_seed_type(self):
seed_type = 'standard' if self.config.get('nosegwit') else 'segwit'
seed_type = 'standard' if self.config.WIZARD_DONT_CREATE_SEGWIT else 'segwit'
self.create_seed(seed_type)
def create_seed(self, seed_type):

15
electrum/coinchooser.py

@ -24,7 +24,7 @@
# SOFTWARE.
from collections import defaultdict
from math import floor, log10
from typing import NamedTuple, List, Callable, Sequence, Union, Dict, Tuple, Mapping, Type
from typing import NamedTuple, List, Callable, Sequence, Union, Dict, Tuple, Mapping, Type, TYPE_CHECKING
from decimal import Decimal
from .bitcoin import sha256, COIN, is_address
@ -32,6 +32,9 @@ from .transaction import Transaction, TxOutput, PartialTransaction, PartialTxInp
from .util import NotEnoughFunds
from .logging import Logger
if TYPE_CHECKING:
from .simple_config import SimpleConfig
# A simple deterministic PRNG. Used to deterministically shuffle a
# set of coins - the same set of coins should produce the same output.
@ -484,13 +487,13 @@ COIN_CHOOSERS = {
'Privacy': CoinChooserPrivacy,
} # type: Mapping[str, Type[CoinChooserBase]]
def get_name(config):
kind = config.get('coin_chooser')
def get_name(config: 'SimpleConfig') -> str:
kind = config.WALLET_COIN_CHOOSER_POLICY
if kind not in COIN_CHOOSERS:
kind = 'Privacy'
kind = config.cv.WALLET_COIN_CHOOSER_POLICY.get_default_value()
return kind
def get_coin_chooser(config) -> CoinChooserBase:
def get_coin_chooser(config: 'SimpleConfig') -> CoinChooserBase:
klass = COIN_CHOOSERS[get_name(config)]
# note: we enable enable_output_value_rounding by default as
# - for sacrificing a few satoshis
@ -498,6 +501,6 @@ def get_coin_chooser(config) -> CoinChooserBase:
# + it also helps the network as a whole as fees will become noisier
# (trying to counter the heuristic that "whole integer sat/byte feerates" are common)
coinchooser = klass(
enable_output_value_rounding=config.get('coin_chooser_output_rounding', True),
enable_output_value_rounding=config.WALLET_COIN_CHOOSER_OUTPUT_ROUNDING,
)
return coinchooser

47
electrum/commands.py

@ -308,7 +308,7 @@ class Commands:
@classmethod
def _setconfig_normalize_value(cls, key, value):
if key not in ('rpcuser', 'rpcpassword'):
if key not in (SimpleConfig.RPC_USERNAME.key(), SimpleConfig.RPC_PASSWORD.key()):
value = json_decode(value)
# call literal_eval for backward compatibility (see #4225)
try:
@ -321,9 +321,9 @@ class Commands:
async def setconfig(self, key, value):
"""Set a configuration variable. 'value' may be a string or a Python expression."""
value = self._setconfig_normalize_value(key, value)
if self.daemon and key == 'rpcuser':
if self.daemon and key == SimpleConfig.RPC_USERNAME.key():
self.daemon.commands_server.rpc_user = value
if self.daemon and key == 'rpcpassword':
if self.daemon and key == SimpleConfig.RPC_PASSWORD.key():
self.daemon.commands_server.rpc_password = value
self.config.set_key(key, value)
return True
@ -1149,7 +1149,7 @@ class Commands:
@command('wl')
async def nodeid(self, wallet: Abstract_Wallet = None):
listen_addr = self.config.get('lightning_listen')
listen_addr = self.config.LIGHTNING_LISTEN
return wallet.lnworker.node_keypair.pubkey.hex() + (('@' + listen_addr) if listen_addr else '')
@command('wl')
@ -1545,13 +1545,14 @@ argparse._SubParsersAction.__call__ = subparser_call
def add_network_options(parser):
parser.add_argument("-f", "--serverfingerprint", dest="serverfingerprint", default=None, help="only allow connecting to servers with a matching SSL certificate SHA256 fingerprint." + " " +
"To calculate this yourself: '$ openssl x509 -noout -fingerprint -sha256 -inform pem -in mycertfile.crt'. Enter as 64 hex chars.")
parser.add_argument("-1", "--oneserver", action="store_true", dest="oneserver", default=None, help="connect to one server only")
parser.add_argument("-s", "--server", dest="server", default=None, help="set server host:port:protocol, where protocol is either t (tcp) or s (ssl)")
parser.add_argument("-p", "--proxy", dest="proxy", default=None, help="set proxy [type:]host[:port] (or 'none' to disable proxy), where type is socks4,socks5 or http")
parser.add_argument("--noonion", action="store_true", dest="noonion", default=None, help="do not try to connect to onion servers")
parser.add_argument("--skipmerklecheck", action="store_true", dest="skipmerklecheck", default=None, help="Tolerate invalid merkle proofs from server")
parser.add_argument("-f", "--serverfingerprint", dest=SimpleConfig.NETWORK_SERVERFINGERPRINT.key(), default=None,
help="only allow connecting to servers with a matching SSL certificate SHA256 fingerprint. " +
"To calculate this yourself: '$ openssl x509 -noout -fingerprint -sha256 -inform pem -in mycertfile.crt'. Enter as 64 hex chars.")
parser.add_argument("-1", "--oneserver", action="store_true", dest=SimpleConfig.NETWORK_ONESERVER.key(), default=None, help="connect to one server only")
parser.add_argument("-s", "--server", dest=SimpleConfig.NETWORK_SERVER.key(), default=None, help="set server host:port:protocol, where protocol is either t (tcp) or s (ssl)")
parser.add_argument("-p", "--proxy", dest=SimpleConfig.NETWORK_PROXY.key(), default=None, help="set proxy [type:]host[:port] (or 'none' to disable proxy), where type is socks4,socks5 or http")
parser.add_argument("--noonion", action="store_true", dest=SimpleConfig.NETWORK_NOONION.key(), default=None, help="do not try to connect to onion servers")
parser.add_argument("--skipmerklecheck", action="store_true", dest=SimpleConfig.NETWORK_SKIPMERKLECHECK.key(), default=None, help="Tolerate invalid merkle proofs from server")
def add_global_options(parser):
group = parser.add_argument_group('global options')
@ -1563,13 +1564,13 @@ def add_global_options(parser):
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("--signet", action="store_true", dest="signet", default=False, help="Use Signet")
group.add_argument("-o", "--offline", action="store_true", dest="offline", default=False, help="Run offline")
group.add_argument("--rpcuser", dest="rpcuser", default=argparse.SUPPRESS, help="RPC user")
group.add_argument("--rpcpassword", dest="rpcpassword", default=argparse.SUPPRESS, help="RPC password")
group.add_argument("-o", "--offline", action="store_true", dest=SimpleConfig.NETWORK_OFFLINE.key(), default=None, help="Run offline")
group.add_argument("--rpcuser", dest=SimpleConfig.RPC_USERNAME.key(), default=argparse.SUPPRESS, help="RPC user")
group.add_argument("--rpcpassword", dest=SimpleConfig.RPC_PASSWORD.key(), default=argparse.SUPPRESS, help="RPC password")
def add_wallet_option(parser):
parser.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path")
parser.add_argument("--forgetconfig", action="store_true", dest="forget_config", default=False, help="Forget config on exit")
parser.add_argument("--forgetconfig", action="store_true", dest=SimpleConfig.CONFIG_FORGET_CHANGES.key(), default=False, help="Forget config on exit")
def get_parser():
# create main parser
@ -1582,11 +1583,11 @@ def get_parser():
# gui
parser_gui = subparsers.add_parser('gui', description="Run Electrum's Graphical User Interface.", help="Run GUI (default)")
parser_gui.add_argument("url", nargs='?', default=None, help="bitcoin URI (or bip70 file)")
parser_gui.add_argument("-g", "--gui", dest="gui", help="select graphical user interface", choices=['qt', 'kivy', 'text', 'stdio', 'qml'])
parser_gui.add_argument("-m", action="store_true", dest="hide_gui", default=False, help="hide GUI on startup")
parser_gui.add_argument("-L", "--lang", dest="language", default=None, help="default language used in GUI")
parser_gui.add_argument("-g", "--gui", dest=SimpleConfig.GUI_NAME.key(), help="select graphical user interface", choices=['qt', 'kivy', 'text', 'stdio', 'qml'])
parser_gui.add_argument("-m", action="store_true", dest=SimpleConfig.GUI_QT_HIDE_ON_STARTUP.key(), default=False, help="hide GUI on startup")
parser_gui.add_argument("-L", "--lang", dest=SimpleConfig.LOCALIZATION_LANGUAGE.key(), default=None, help="default language used in GUI")
parser_gui.add_argument("--daemon", action="store_true", dest="daemon", default=False, help="keep daemon running after GUI is closed")
parser_gui.add_argument("--nosegwit", action="store_true", dest="nosegwit", default=False, help="Do not create segwit wallets")
parser_gui.add_argument("--nosegwit", action="store_true", dest=SimpleConfig.WIZARD_DONT_CREATE_SEGWIT.key(), default=False, help="Do not create segwit wallets")
add_wallet_option(parser_gui)
add_network_options(parser_gui)
add_global_options(parser_gui)
@ -1595,10 +1596,10 @@ def get_parser():
parser_daemon.add_argument("-d", "--detached", action="store_true", dest="detach", default=False, help="run daemon in detached mode")
# FIXME: all these options are rpc-server-side. The CLI client-side cannot use e.g. --rpcport,
# instead it reads it from the daemon lockfile.
parser_daemon.add_argument("--rpchost", dest="rpchost", default=argparse.SUPPRESS, help="RPC host")
parser_daemon.add_argument("--rpcport", dest="rpcport", type=int, default=argparse.SUPPRESS, help="RPC port")
parser_daemon.add_argument("--rpcsock", dest="rpcsock", default=None, help="what socket type to which to bind RPC daemon", choices=['unix', 'tcp', 'auto'])
parser_daemon.add_argument("--rpcsockpath", dest="rpcsockpath", help="where to place RPC file socket")
parser_daemon.add_argument("--rpchost", dest=SimpleConfig.RPC_HOST.key(), default=argparse.SUPPRESS, help="RPC host")
parser_daemon.add_argument("--rpcport", dest=SimpleConfig.RPC_PORT.key(), type=int, default=argparse.SUPPRESS, help="RPC port")
parser_daemon.add_argument("--rpcsock", dest=SimpleConfig.RPC_SOCKET_TYPE.key(), default=None, help="what socket type to which to bind RPC daemon", choices=['unix', 'tcp', 'auto'])
parser_daemon.add_argument("--rpcsockpath", dest=SimpleConfig.RPC_SOCKET_FILEPATH.key(), help="where to place RPC file socket")
add_network_options(parser_daemon)
add_global_options(parser_daemon)
# commands

2
electrum/contacts.py

@ -98,7 +98,7 @@ class Contacts(dict, Logger):
def fetch_openalias(self, config):
self.alias_info = None
alias = config.get('alias')
alias = config.OPENALIAS_ID
if alias:
alias = str(alias)
def f():

42
electrum/daemon.py

@ -69,7 +69,7 @@ def get_rpcsock_defaultpath(config: SimpleConfig):
return os.path.join(config.path, 'daemon_rpc_socket')
def get_rpcsock_default_type(config: SimpleConfig):
if config.get('rpcport'):
if config.RPC_PORT:
return 'tcp'
# Use unix domain sockets when available,
# with the extra paranoia that in case windows "implements" them,
@ -106,7 +106,7 @@ def get_file_descriptor(config: SimpleConfig):
def request(config: SimpleConfig, endpoint, args=(), timeout=60):
def request(config: SimpleConfig, endpoint, args=(), timeout: Union[float, int] = 60):
lockfile = get_lockfile(config)
while True:
create_time = None
@ -152,12 +152,8 @@ def request(config: SimpleConfig, endpoint, args=(), timeout=60):
def get_rpc_credentials(config: SimpleConfig) -> Tuple[str, str]:
rpc_user = config.get('rpcuser', None)
rpc_password = config.get('rpcpassword', None)
if rpc_user == '':
rpc_user = None
if rpc_password == '':
rpc_password = None
rpc_user = config.RPC_USERNAME or None
rpc_password = config.RPC_PASSWORD or None
if rpc_user is None or rpc_password is None:
rpc_user = 'user'
bits = 128
@ -166,8 +162,8 @@ def get_rpc_credentials(config: SimpleConfig) -> Tuple[str, str]:
pw_b64 = b64encode(
pw_int.to_bytes(nbytes, 'big'), b'-_')
rpc_password = to_string(pw_b64, 'ascii')
config.set_key('rpcuser', rpc_user)
config.set_key('rpcpassword', rpc_password, save=True)
config.RPC_USERNAME = rpc_user
config.RPC_PASSWORD = rpc_password
return rpc_user, rpc_password
@ -252,17 +248,17 @@ class AuthenticatedServer(Logger):
class CommandsServer(AuthenticatedServer):
def __init__(self, daemon, fd):
def __init__(self, daemon: 'Daemon', fd):
rpc_user, rpc_password = get_rpc_credentials(daemon.config)
AuthenticatedServer.__init__(self, rpc_user, rpc_password)
self.daemon = daemon
self.fd = fd
self.config = daemon.config
sockettype = self.config.get('rpcsock', 'auto')
sockettype = self.config.RPC_SOCKET_TYPE
self.socktype = sockettype if sockettype != 'auto' else get_rpcsock_default_type(self.config)
self.sockpath = self.config.get('rpcsockpath', get_rpcsock_defaultpath(self.config))
self.host = self.config.get('rpchost', '127.0.0.1')
self.port = self.config.get('rpcport', 0)
self.sockpath = self.config.RPC_SOCKET_FILEPATH or get_rpcsock_defaultpath(self.config)
self.host = self.config.RPC_HOST
self.port = self.config.RPC_PORT
self.app = web.Application()
self.app.router.add_post("/", self.handle)
self.register_method(self.ping)
@ -348,12 +344,12 @@ class CommandsServer(AuthenticatedServer):
class WatchTowerServer(AuthenticatedServer):
def __init__(self, network, netaddress):
def __init__(self, network: 'Network', netaddress):
self.addr = netaddress
self.config = network.config
self.network = network
watchtower_user = self.config.get('watchtower_user', '')
watchtower_password = self.config.get('watchtower_password', '')
watchtower_user = self.config.WATCHTOWER_SERVER_USER or ""
watchtower_password = self.config.WATCHTOWER_SERVER_PASSWORD or ""
AuthenticatedServer.__init__(self, watchtower_user, watchtower_password)
self.lnwatcher = network.local_watchtower
self.app = web.Application()
@ -403,7 +399,7 @@ class Daemon(Logger):
self.logger.warning("Ignoring parameter 'wallet_path' for daemon. "
"Use the load_wallet command instead.")
self.asyncio_loop = util.get_asyncio_loop()
if not config.get('offline'):
if not self.config.NETWORK_OFFLINE:
self.network = Network(config, daemon=self)
self.fx = FxThread(config=config)
# path -> wallet; make sure path is standardized.
@ -444,16 +440,16 @@ class Daemon(Logger):
def start_network(self):
self.logger.info(f"starting network.")
assert not self.config.get('offline')
assert not self.config.NETWORK_OFFLINE
assert self.network
# server-side watchtower
if watchtower_address := self.config.get_netaddress('watchtower_address'):
if watchtower_address := self.config.get_netaddress(self.config.cv.WATCHTOWER_SERVER_ADDRESS):
self.watchtower = WatchTowerServer(self.network, watchtower_address)
asyncio.run_coroutine_threadsafe(self.taskgroup.spawn(self.watchtower.run), self.asyncio_loop)
self.network.start(jobs=[self.fx.run])
# prepare lightning functionality, also load channel db early
if self.config.get('use_gossip', False):
if self.config.LIGHTNING_USE_GOSSIP:
self.network.start_gossip()
def with_wallet_lock(func):
@ -582,7 +578,7 @@ class Daemon(Logger):
def run_gui(self, config: 'SimpleConfig', plugins: 'Plugins'):
threading.current_thread().name = 'GUI'
gui_name = config.get('gui', 'qt')
gui_name = config.GUI_NAME
if gui_name in ['lite', 'classic']:
gui_name = 'qt'
self.logger.info(f'launching GUI: {gui_name}')

27
electrum/exchange_rate.py

@ -23,11 +23,6 @@ from .simple_config import SimpleConfig
from .logging import Logger
DEFAULT_ENABLED = False
DEFAULT_CURRENCY = "EUR"
DEFAULT_EXCHANGE = "CoinGecko" # default exchange should ideally provide historical rates
# See https://en.wikipedia.org/wiki/ISO_4217
CCY_PRECISIONS = {'BHD': 3, 'BIF': 0, 'BYR': 0, 'CLF': 4, 'CLP': 0,
'CVE': 0, 'DJF': 0, 'GNF': 0, 'IQD': 3, 'ISK': 0,
@ -577,29 +572,29 @@ class FxThread(ThreadJob, EventListener):
if self.is_enabled():
await self.exchange.update_safe(self.ccy)
def is_enabled(self):
return bool(self.config.get('use_exchange_rate', DEFAULT_ENABLED))
def is_enabled(self) -> bool:
return self.config.FX_USE_EXCHANGE_RATE
def set_enabled(self, b):
self.config.set_key('use_exchange_rate', bool(b))
def set_enabled(self, b: bool) -> None:
self.config.FX_USE_EXCHANGE_RATE = b
self.trigger_update()
def can_have_history(self):
return self.is_enabled() and self.ccy in self.exchange.history_ccys()
def has_history(self) -> bool:
return self.can_have_history() and bool(self.config.get('history_rates', False))
return self.can_have_history() and self.config.FX_HISTORY_RATES
def get_currency(self) -> str:
'''Use when dynamic fetching is needed'''
return self.config.get("currency", DEFAULT_CURRENCY)
return self.config.FX_CURRENCY
def config_exchange(self):
return self.config.get('use_exchange', DEFAULT_EXCHANGE)
return self.config.FX_EXCHANGE
def set_currency(self, ccy: str):
self.ccy = ccy
self.config.set_key('currency', ccy, save=True)
self.config.FX_CURRENCY = ccy
self.trigger_update()
self.on_quotes()
@ -608,10 +603,10 @@ class FxThread(ThreadJob, EventListener):
loop.call_soon_threadsafe(self._trigger.set)
def set_exchange(self, name):
class_ = globals().get(name) or globals().get(DEFAULT_EXCHANGE)
class_ = globals().get(name) or globals().get(self.config.cv.FX_EXCHANGE.get_default_value())
self.logger.info(f"using exchange {name}")
if self.config_exchange() != name:
self.config.set_key('use_exchange', name, save=True)
self.config.FX_EXCHANGE = name
assert issubclass(class_, ExchangeBase), f"unexpected type {class_} for {name}"
self.exchange = class_(self.on_quotes, self.on_history) # type: ExchangeBase
# A new exchange means new fx quotes, initially empty. Force
@ -689,4 +684,4 @@ class FxThread(ThreadJob, EventListener):
return self.history_rate(date)
assert globals().get(DEFAULT_EXCHANGE), f"default exchange {DEFAULT_EXCHANGE} does not exist"
assert globals().get(SimpleConfig.FX_EXCHANGE.get_default_value()), f"default exchange {SimpleConfig.FX_EXCHANGE.get_default_value()} does not exist"

34
electrum/gui/kivy/main_window.py

@ -25,6 +25,7 @@ from electrum.network import Network, TxBroadcastError, BestEffortRequestFailed
from electrum.interface import PREFERRED_NETWORK_PROTOCOL, ServerAddr
from electrum.logging import Logger
from electrum.bitcoin import COIN
from electrum.simple_config import SimpleConfig
from electrum.gui import messages
from .i18n import _
@ -94,7 +95,6 @@ from .uix.dialogs.lightning_channels import LightningChannelsDialog, SwapDialog
if TYPE_CHECKING:
from . import ElectrumGui
from electrum.simple_config import SimpleConfig
from electrum.plugin import Plugins
from electrum.paymentrequest import PaymentRequest
@ -133,7 +133,7 @@ class ElectrumWindow(App, Logger, EventListener):
def set_auto_connect(self, b: bool):
# This method makes sure we persist x into the config even if self.auto_connect == b.
# Note: on_auto_connect() only gets called if the value of the self.auto_connect property *changes*.
self.electrum_config.set_key('auto_connect', b)
self.electrum_config.NETWORK_AUTO_CONNECT = b
self.auto_connect = b
def toggle_auto_connect(self, x):
@ -196,7 +196,7 @@ class ElectrumWindow(App, Logger, EventListener):
use_gossip = BooleanProperty(False)
def on_use_gossip(self, instance, x):
self.electrum_config.set_key('use_gossip', self.use_gossip, save=True)
self.electrum_config.LIGHTNING_USE_GOSSIP = self.use_gossip
if self.network:
if self.use_gossip:
self.network.start_gossip()
@ -206,7 +206,7 @@ class ElectrumWindow(App, Logger, EventListener):
enable_debug_logs = BooleanProperty(False)
def on_enable_debug_logs(self, instance, x):
self.electrum_config.set_key('gui_enable_debug_logs', self.enable_debug_logs, save=True)
self.electrum_config.GUI_ENABLE_DEBUG_LOGS = self.enable_debug_logs
use_change = BooleanProperty(False)
def on_use_change(self, instance, x):
@ -217,11 +217,11 @@ class ElectrumWindow(App, Logger, EventListener):
use_unconfirmed = BooleanProperty(False)
def on_use_unconfirmed(self, instance, x):
self.electrum_config.set_key('confirmed_only', not self.use_unconfirmed, save=True)
self.electrum_config.WALLET_SPEND_CONFIRMED_ONLY = not self.use_unconfirmed
use_recoverable_channels = BooleanProperty(True)
def on_use_recoverable_channels(self, instance, x):
self.electrum_config.set_key('use_recoverable_channels', self.use_recoverable_channels, save=True)
self.electrum_config.LIGHTNING_USE_RECOVERABLE_CHANNELS = self.use_recoverable_channels
def switch_to_send_screen(func):
# try until send_screen is available
@ -414,7 +414,7 @@ class ElectrumWindow(App, Logger, EventListener):
Logger.__init__(self)
self.electrum_config = config = kwargs.get('config', None) # type: SimpleConfig
self.language = config.get('language', get_default_language())
self.language = config.LOCALIZATION_LANGUAGE or get_default_language()
self.network = network = kwargs.get('network', None) # type: Network
if self.network:
self.num_blocks = self.network.get_local_height()
@ -431,9 +431,9 @@ class ElectrumWindow(App, Logger, EventListener):
self.gui_object = kwargs.get('gui_object', None) # type: ElectrumGui
self.daemon = self.gui_object.daemon
self.fx = self.daemon.fx
self.use_gossip = config.get('use_gossip', False)
self.use_unconfirmed = not config.get('confirmed_only', False)
self.enable_debug_logs = config.get('gui_enable_debug_logs', False)
self.use_gossip = config.LIGHTNING_USE_GOSSIP
self.use_unconfirmed = not config.WALLET_SPEND_CONFIRMED_ONLY
self.enable_debug_logs = config.GUI_ENABLE_DEBUG_LOGS
# create triggers so as to minimize updating a max of 2 times a sec
self._trigger_update_wallet = Clock.create_trigger(self.update_wallet, .5)
@ -644,7 +644,7 @@ class ElectrumWindow(App, Logger, EventListener):
self.on_new_intent(mactivity.getIntent())
activity.bind(on_new_intent=self.on_new_intent)
self.register_callbacks()
if self.network and self.electrum_config.get('auto_connect') is None:
if self.network and not self.electrum_config.cv.NETWORK_AUTO_CONNECT.is_set():
self.popup_dialog("first_screen")
# load_wallet_on_start will be called later, after initial network setup is completed
else:
@ -676,7 +676,7 @@ class ElectrumWindow(App, Logger, EventListener):
def on_wizard_success(self, storage, db, password):
self.password = password
if self.electrum_config.get('single_password'):
if self.electrum_config.WALLET_USE_SINGLE_PASSWORD:
self._use_single_password = self.daemon.update_password_for_directory(
old_password=password, new_password=password)
self.logger.info(f'use single password: {self._use_single_password}')
@ -811,7 +811,7 @@ class ElectrumWindow(App, Logger, EventListener):
Clock.schedule_once(lambda dt: self._channels_dialog.update())
def is_wallet_creation_disabled(self):
return bool(self.electrum_config.get('single_password')) and self.password is None
return self.electrum_config.WALLET_USE_SINGLE_PASSWORD and self.password is None
def wallets_dialog(self):
from .uix.dialogs.wallets import WalletDialog
@ -1278,7 +1278,7 @@ class ElectrumWindow(App, Logger, EventListener):
self.set_fee_status()
def protected(self, msg, f, args):
if self.electrum_config.get('pin_code'):
if self.electrum_config.CONFIG_PIN_CODE:
msg += "\n" + _("Enter your PIN code to proceed")
on_success = lambda pw: f(*args, self.password)
d = PincodeDialog(
@ -1337,10 +1337,10 @@ class ElectrumWindow(App, Logger, EventListener):
label.data += '\n\n' + _('Passphrase') + ': ' + passphrase
def has_pin_code(self):
return bool(self.electrum_config.get('pin_code'))
return bool(self.electrum_config.CONFIG_PIN_CODE)
def check_pin_code(self, pin):
if pin != self.electrum_config.get('pin_code'):
if pin != self.electrum_config.CONFIG_PIN_CODE:
raise InvalidPassword
def change_password(self, cb):
@ -1386,7 +1386,7 @@ class ElectrumWindow(App, Logger, EventListener):
d.open()
def _set_new_pin_code(self, new_pin, cb):
self.electrum_config.set_key('pin_code', new_pin)
self.electrum_config.CONFIG_PIN_CODE = new_pin
cb()
self.show_info(_("PIN updated") if new_pin else _('PIN disabled'))

12
electrum/gui/kivy/uix/dialogs/crash_reporter.py

@ -1,5 +1,6 @@
import sys
import json
from typing import TYPE_CHECKING
from aiohttp.client_exceptions import ClientError
from kivy import base, utils
@ -15,6 +16,9 @@ from electrum.gui.kivy.i18n import _
from electrum.base_crash_reporter import BaseCrashReporter, EarlyExceptionsQueue
from electrum.logging import Logger
if TYPE_CHECKING:
from electrum.gui.kivy.main_window import ElectrumWindow
Builder.load_string('''
<CrashReporter@Popup>
@ -95,7 +99,7 @@ class CrashReporter(BaseCrashReporter, Factory.Popup):
* Locale: {locale}
"""
def __init__(self, main_window, exctype, value, tb):
def __init__(self, main_window: 'ElectrumWindow', exctype, value, tb):
BaseCrashReporter.__init__(self, exctype, value, tb)
Factory.Popup.__init__(self)
self.main_window = main_window
@ -156,7 +160,7 @@ class CrashReporter(BaseCrashReporter, Factory.Popup):
currentActivity.startActivity(browserIntent)
def show_never(self):
self.main_window.electrum_config.set_key(BaseCrashReporter.config_key, False)
self.main_window.electrum_config.SHOW_CRASH_REPORTER = False
self.dismiss()
def get_user_description(self):
@ -175,11 +179,11 @@ class CrashReportDetails(Factory.Popup):
class ExceptionHook(base.ExceptionHandler, Logger):
def __init__(self, main_window):
def __init__(self, main_window: 'ElectrumWindow'):
base.ExceptionHandler.__init__(self)
Logger.__init__(self)
self.main_window = main_window
if not main_window.electrum_config.get(BaseCrashReporter.config_key, default=True):
if not main_window.electrum_config.SHOW_CRASH_REPORTER:
EarlyExceptionsQueue.set_hook_as_ready() # flush already queued exceptions
return
# For exceptions in Kivy:

10
electrum/gui/kivy/uix/dialogs/fee_dialog.py

@ -99,15 +99,15 @@ class FeeSliderDialog:
def save_config(self):
value = int(self.slider.value)
dynfees, mempool = self.get_method()
self.config.set_key('dynamic_fees', dynfees, save=False)
self.config.set_key('mempool_fees', mempool, save=False)
self.config.FEE_EST_DYNAMIC = dynfees
self.config.FEE_EST_USE_MEMPOOL = mempool
if dynfees:
if mempool:
self.config.set_key('depth_level', value, save=True)
self.config.FEE_EST_DYNAMIC_MEMPOOL_SLIDERPOS = value
else:
self.config.set_key('fee_level', value, save=True)
self.config.FEE_EST_DYNAMIC_ETA_SLIDERPOS = value
else:
self.config.set_key('fee_per_kb', self.config.static_fee(value), save=True)
self.config.FEE_EST_STATIC_FEERATE_FALLBACK = self.config.static_fee(value)
def update_text(self):
pass

8
electrum/gui/kivy/uix/dialogs/settings.py

@ -146,7 +146,7 @@ class SettingsDialog(Factory.Popup):
self.enable_toggle_use_recoverable_channels = bool(self.wallet.lnworker and self.wallet.lnworker.can_have_recoverable_channels())
def get_language_name(self) -> str:
lang = self.config.get('language') or ''
lang = self.config.LOCALIZATION_LANGUAGE
return languages.get(lang) or languages.get('') or ''
def change_password(self, dt):
@ -157,9 +157,9 @@ class SettingsDialog(Factory.Popup):
def language_dialog(self, item, dt):
if self._language_dialog is None:
l = self.config.get('language') or ''
l = self.config.LOCALIZATION_LANGUAGE
def cb(key):
self.config.set_key("language", key, save=True)
self.config.LOCALIZATION_LANGUAGE = key
item.lang = self.get_language_name()
self.app.language = key
self._language_dialog = ChoiceDialog(_('Language'), languages, l, cb)
@ -194,7 +194,7 @@ class SettingsDialog(Factory.Popup):
choosers = sorted(coinchooser.COIN_CHOOSERS.keys())
chooser_name = coinchooser.get_name(self.config)
def cb(text):
self.config.set_key('coin_chooser', text)
self.config.WALLET_COIN_CHOOSER_POLICY = text
item.status = text
self._coinselect_dialog = ChoiceDialog(_('Coin selection'), choosers, chooser_name, cb)
self._coinselect_dialog.open()

4
electrum/gui/kivy/uix/screens.py

@ -480,7 +480,7 @@ class ReceiveScreen(CScreen):
self.expiration_text = pr_expiration_values[c]
def expiry(self):
return self.app.electrum_config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
return self.app.electrum_config.WALLET_PAYREQ_EXPIRY_SECONDS
def clear(self):
self.address = ''
@ -587,7 +587,7 @@ class ReceiveScreen(CScreen):
def expiration_dialog(self, obj):
from .dialogs.choice_dialog import ChoiceDialog
def callback(c):
self.app.electrum_config.set_key('request_expiry', c)
self.app.electrum_config.WALLET_PAYREQ_EXPIRY_SECONDS = c
self.expiration_text = pr_expiration_values[c]
d = ChoiceDialog(_('Expiration date'), pr_expiration_values, self.expiry(), callback)
d.open()

4
electrum/gui/qml/qeapp.py

@ -272,7 +272,7 @@ class QEAppController(BaseCrashReporter, QObject):
@pyqtSlot()
def showNever(self):
self.config.set_key(BaseCrashReporter.config_key, False)
self.config.SHOW_CRASH_REPORTER = False
@pyqtSlot(str)
def setCrashUserText(self, text):
@ -425,7 +425,7 @@ class Exception_Hook(QObject, Logger):
@classmethod
def maybe_setup(cls, *, config: 'SimpleConfig', wallet: 'Abstract_Wallet' = None, slot = None) -> None:
if not config.get(BaseCrashReporter.config_key, default=True):
if not config.SHOW_CRASH_REPORTER:
EarlyExceptionsQueue.set_hook_as_ready() # flush already queued exceptions
return
if not cls._INSTANCE:

5
electrum/gui/qml/qechannelopener.py

@ -1,6 +1,7 @@
import threading
from concurrent.futures import CancelledError
from asyncio.exceptions import TimeoutError
from typing import TYPE_CHECKING, Optional
from PyQt5.QtCore import pyqtProperty, pyqtSignal, pyqtSlot, QObject
@ -32,7 +33,7 @@ class QEChannelOpener(QObject, AuthMixin):
def __init__(self, parent=None):
super().__init__(parent)
self._wallet = None
self._wallet = None # type: Optional[QEWallet]
self._connect_str = None
self._amount = QEAmount()
self._valid = False
@ -101,7 +102,7 @@ class QEChannelOpener(QObject, AuthMixin):
connect_str_valid = False
if self._connect_str:
self._logger.debug(f'checking if {self._connect_str=!r} is valid')
if not self._wallet.wallet.config.get('use_gossip', False):
if not self._wallet.wallet.config.LIGHTNING_USE_GOSSIP:
# using trampoline: connect_str is the name of a trampoline node
peer_addr = hardcoded_trampoline_nodes()[self._connect_str]
self._node_pubkey = peer_addr.pubkey

82
electrum/gui/qml/qeconfig.py

@ -9,13 +9,11 @@ from electrum.i18n import set_language, languages
from electrum.logging import get_logger
from electrum.util import DECIMAL_POINT_DEFAULT, base_unit_name_to_decimal_point
from electrum.invoices import PR_DEFAULT_EXPIRATION_WHEN_CREATING
from electrum.simple_config import SimpleConfig
from .qetypes import QEAmount
from .auth import AuthMixin, auth_protect
if TYPE_CHECKING:
from electrum.simple_config import SimpleConfig
class QEConfig(AuthMixin, QObject):
_logger = get_logger(__name__)
@ -27,14 +25,14 @@ class QEConfig(AuthMixin, QObject):
languageChanged = pyqtSignal()
@pyqtProperty(str, notify=languageChanged)
def language(self):
return self.config.get('language')
return self.config.LOCALIZATION_LANGUAGE
@language.setter
def language(self, language):
if language not in languages:
return
if self.config.get('language') != language:
self.config.set_key('language', language)
if self.config.LOCALIZATION_LANGUAGE != language:
self.config.LOCALIZATION_LANGUAGE = language
set_language(language)
self.languageChanged.emit()
@ -51,27 +49,17 @@ class QEConfig(AuthMixin, QObject):
autoConnectChanged = pyqtSignal()
@pyqtProperty(bool, notify=autoConnectChanged)
def autoConnect(self):
return self.config.get('auto_connect')
return self.config.NETWORK_AUTO_CONNECT
@autoConnect.setter
def autoConnect(self, auto_connect):
self.config.set_key('auto_connect', auto_connect, save=True)
self.config.NETWORK_AUTO_CONNECT = auto_connect
self.autoConnectChanged.emit()
# auto_connect is actually a tri-state, expose the undefined case
@pyqtProperty(bool, notify=autoConnectChanged)
def autoConnectDefined(self):
return self.config.get('auto_connect') is not None
manualServerChanged = pyqtSignal()
@pyqtProperty(bool, notify=manualServerChanged)
def manualServer(self):
return self.config.get('oneserver')
@manualServer.setter
def manualServer(self, oneserver):
self.config.set_key('oneserver', oneserver, save=True)
self.manualServerChanged.emit()
return self.config.cv.NETWORK_AUTO_CONNECT.is_set()
baseUnitChanged = pyqtSignal()
@pyqtProperty(str, notify=baseUnitChanged)
@ -98,129 +86,129 @@ class QEConfig(AuthMixin, QObject):
thousandsSeparatorChanged = pyqtSignal()
@pyqtProperty(bool, notify=thousandsSeparatorChanged)
def thousandsSeparator(self):
return self.config.get('amt_add_thousands_sep', False)
return self.config.BTC_AMOUNTS_ADD_THOUSANDS_SEP
@thousandsSeparator.setter
def thousandsSeparator(self, checked):
self.config.set_key('amt_add_thousands_sep', checked)
self.config.BTC_AMOUNTS_ADD_THOUSANDS_SEP = checked
self.config.amt_add_thousands_sep = checked
self.thousandsSeparatorChanged.emit()
spendUnconfirmedChanged = pyqtSignal()
@pyqtProperty(bool, notify=spendUnconfirmedChanged)
def spendUnconfirmed(self):
return not self.config.get('confirmed_only', False)
return not self.config.WALLET_SPEND_CONFIRMED_ONLY
@spendUnconfirmed.setter
def spendUnconfirmed(self, checked):
self.config.set_key('confirmed_only', not checked, save=True)
self.config.WALLET_SPEND_CONFIRMED_ONLY = not checked
self.spendUnconfirmedChanged.emit()
requestExpiryChanged = pyqtSignal()
@pyqtProperty(int, notify=requestExpiryChanged)
def requestExpiry(self):
return self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
return self.config.WALLET_PAYREQ_EXPIRY_SECONDS
@requestExpiry.setter
def requestExpiry(self, expiry):
self.config.set_key('request_expiry', expiry)
self.config.WALLET_PAYREQ_EXPIRY_SECONDS = expiry
self.requestExpiryChanged.emit()
pinCodeChanged = pyqtSignal()
@pyqtProperty(str, notify=pinCodeChanged)
def pinCode(self):
return self.config.get('pin_code', '')
return self.config.CONFIG_PIN_CODE or ""
@pinCode.setter
def pinCode(self, pin_code):
if pin_code == '':
self.pinCodeRemoveAuth()
else:
self.config.set_key('pin_code', pin_code, save=True)
self.config.CONFIG_PIN_CODE = pin_code
self.pinCodeChanged.emit()
@auth_protect(method='wallet')
def pinCodeRemoveAuth(self):
self.config.set_key('pin_code', '', save=True)
self.config.CONFIG_PIN_CODE = ""
self.pinCodeChanged.emit()
useGossipChanged = pyqtSignal()
@pyqtProperty(bool, notify=useGossipChanged)
def useGossip(self):
return self.config.get('use_gossip', False)
return self.config.LIGHTNING_USE_GOSSIP
@useGossip.setter
def useGossip(self, gossip):
self.config.set_key('use_gossip', gossip)
self.config.LIGHTNING_USE_GOSSIP = gossip
self.useGossipChanged.emit()
useFallbackAddressChanged = pyqtSignal()
@pyqtProperty(bool, notify=useFallbackAddressChanged)
def useFallbackAddress(self):
return self.config.get('bolt11_fallback', True)
return self.config.WALLET_BOLT11_FALLBACK
@useFallbackAddress.setter
def useFallbackAddress(self, use_fallback):
self.config.set_key('bolt11_fallback', use_fallback)
self.config.WALLET_BOLT11_FALLBACK = use_fallback
self.useFallbackAddressChanged.emit()
enableDebugLogsChanged = pyqtSignal()
@pyqtProperty(bool, notify=enableDebugLogsChanged)
def enableDebugLogs(self):
gui_setting = self.config.get('gui_enable_debug_logs', False)
gui_setting = self.config.GUI_ENABLE_DEBUG_LOGS
return gui_setting or bool(self.config.get('verbosity'))
@pyqtProperty(bool, notify=enableDebugLogsChanged)
def canToggleDebugLogs(self):
gui_setting = self.config.get('gui_enable_debug_logs', False)
gui_setting = self.config.GUI_ENABLE_DEBUG_LOGS
return not self.config.get('verbosity') or gui_setting
@enableDebugLogs.setter
def enableDebugLogs(self, enable):
self.config.set_key('gui_enable_debug_logs', enable)
self.config.GUI_ENABLE_DEBUG_LOGS = enable
self.enableDebugLogsChanged.emit()
useRecoverableChannelsChanged = pyqtSignal()
@pyqtProperty(bool, notify=useRecoverableChannelsChanged)
def useRecoverableChannels(self):
return self.config.get('use_recoverable_channels', True)
return self.config.LIGHTNING_USE_RECOVERABLE_CHANNELS
@useRecoverableChannels.setter
def useRecoverableChannels(self, useRecoverableChannels):
self.config.set_key('use_recoverable_channels', useRecoverableChannels)
self.config.LIGHTNING_USE_RECOVERABLE_CHANNELS = useRecoverableChannels
self.useRecoverableChannelsChanged.emit()
trustedcoinPrepayChanged = pyqtSignal()
@pyqtProperty(int, notify=trustedcoinPrepayChanged)
def trustedcoinPrepay(self):
return self.config.get('trustedcoin_prepay', 20)
return self.config.PLUGIN_TRUSTEDCOIN_NUM_PREPAY
@trustedcoinPrepay.setter
def trustedcoinPrepay(self, num_prepay):
if num_prepay != self.config.get('trustedcoin_prepay', 20):
self.config.set_key('trustedcoin_prepay', num_prepay)
if num_prepay != self.config.PLUGIN_TRUSTEDCOIN_NUM_PREPAY:
self.config.PLUGIN_TRUSTEDCOIN_NUM_PREPAY = num_prepay
self.trustedcoinPrepayChanged.emit()
preferredRequestTypeChanged = pyqtSignal()
@pyqtProperty(str, notify=preferredRequestTypeChanged)
def preferredRequestType(self):
return self.config.get('preferred_request_type', 'bolt11')
return self.config.GUI_QML_PREFERRED_REQUEST_TYPE
@preferredRequestType.setter
def preferredRequestType(self, preferred_request_type):
if preferred_request_type != self.config.get('preferred_request_type', 'bolt11'):
self.config.set_key('preferred_request_type', preferred_request_type)
if preferred_request_type != self.config.GUI_QML_PREFERRED_REQUEST_TYPE:
self.config.GUI_QML_PREFERRED_REQUEST_TYPE = preferred_request_type
self.preferredRequestTypeChanged.emit()
userKnowsPressAndHoldChanged = pyqtSignal()
@pyqtProperty(bool, notify=userKnowsPressAndHoldChanged)
def userKnowsPressAndHold(self):
return self.config.get('user_knows_press_and_hold', False)
return self.config.GUI_QML_USER_KNOWS_PRESS_AND_HOLD
@userKnowsPressAndHold.setter
def userKnowsPressAndHold(self, userKnowsPressAndHold):
if userKnowsPressAndHold != self.config.get('user_knows_press_and_hold', False):
self.config.set_key('user_knows_press_and_hold', userKnowsPressAndHold)
if userKnowsPressAndHold != self.config.GUI_QML_USER_KNOWS_PRESS_AND_HOLD:
self.config.GUI_QML_USER_KNOWS_PRESS_AND_HOLD = userKnowsPressAndHold
self.userKnowsPressAndHoldChanged.emit()
@ -251,7 +239,7 @@ class QEConfig(AuthMixin, QObject):
# TODO delegate all this to config.py/util.py
def decimal_point(self):
return self.config.get('decimal_point', DECIMAL_POINT_DEFAULT)
return self.config.BTC_AMOUNTS_DECIMAL_POINT
def max_precision(self):
return self.decimal_point() + 0 #self.extra_precision

4
electrum/gui/qml/qedaemon.py

@ -162,7 +162,7 @@ class QEDaemon(AuthMixin, QObject):
if path is None:
self._path = self.daemon.config.get('wallet_path') # command line -w option
if self._path is None:
self._path = self.daemon.config.get('gui_last_wallet')
self._path = self.daemon.config.GUI_LAST_WALLET
else:
self._path = path
if self._path is None:
@ -208,7 +208,7 @@ class QEDaemon(AuthMixin, QObject):
# we need the correct current wallet password below
local_password = QEWallet.getInstanceFor(wallet).password
if self.daemon.config.get('single_password'):
if self.daemon.config.WALLET_USE_SINGLE_PASSWORD:
self._use_single_password = self.daemon.update_password_for_directory(old_password=local_password, new_password=local_password)
self._password = local_password
self.singlePasswordChanged.emit()

6
electrum/gui/qml/qefx.py

@ -72,12 +72,14 @@ class QEFX(QObject, QtEventListener):
historicRatesChanged = pyqtSignal()
@pyqtProperty(bool, notify=historicRatesChanged)
def historicRates(self):
return bool(self.fx.config.get('history_rates', True))
if not self.fx.config.cv.FX_HISTORY_RATES.is_set():
self.fx.config.FX_HISTORY_RATES = True # override default
return self.fx.config.FX_HISTORY_RATES
@historicRates.setter
def historicRates(self, checked):
if checked != self.historicRates:
self.fx.config.set_key('history_rates', bool(checked))
self.fx.config.FX_HISTORY_RATES = bool(checked)
self.historicRatesChanged.emit()
self.rateSourcesChanged.emit()

2
electrum/gui/qml/qeinvoice.py

@ -387,7 +387,7 @@ class QEInvoice(QObject, QtEventListener):
def get_max_spendable_onchain(self):
spendable = self._wallet.confirmedBalance.satsInt
if not self._wallet.wallet.config.get('confirmed_only', False):
if not self._wallet.wallet.config.WALLET_SPEND_CONFIRMED_ONLY:
spendable += self._wallet.unconfirmedBalance.satsInt
return spendable

10
electrum/gui/qml/qetxfinalizer.py

@ -110,15 +110,15 @@ class FeeSlider(QObject):
def save_config(self):
value = int(self._sliderPos)
dynfees, mempool = self.get_method()
self._config.set_key('dynamic_fees', dynfees, save=False)
self._config.set_key('mempool_fees', mempool, save=False)
self._config.FEE_EST_DYNAMIC = dynfees
self._config.FEE_EST_USE_MEMPOOL = mempool
if dynfees:
if mempool:
self._config.set_key('depth_level', value, save=True)
self._config.FEE_EST_DYNAMIC_MEMPOOL_SLIDERPOS = value
else:
self._config.set_key('fee_level', value, save=True)
self._config.FEE_EST_DYNAMIC_ETA_SLIDERPOS = value
else:
self._config.set_key('fee_per_kb', self._config.static_fee(value), save=True)
self._config.FEE_EST_STATIC_FEERATE_FALLBACK = self._config.static_fee(value)
self.update_target()
self.update()

10
electrum/gui/qt/__init__.py

@ -63,6 +63,7 @@ from electrum.wallet import Wallet, Abstract_Wallet
from electrum.wallet_db import WalletDB
from electrum.logging import Logger
from electrum.gui import BaseElectrumGui
from electrum.simple_config import SimpleConfig
from .installwizard import InstallWizard, WalletAlreadyOpenInMemory
from .util import read_QIcon, ColorScheme, custom_message_box, MessageBoxMixin
@ -75,7 +76,6 @@ from .exception_window import Exception_Hook
if TYPE_CHECKING:
from electrum.daemon import Daemon
from electrum.simple_config import SimpleConfig
from electrum.plugin import Plugins
@ -139,7 +139,7 @@ class ElectrumGui(BaseElectrumGui, Logger):
self.watchtower_dialog = None
self._num_wizards_in_progress = 0
self._num_wizards_lock = threading.Lock()
self.dark_icon = self.config.get("dark_icon", False)
self.dark_icon = self.config.GUI_QT_DARK_TRAY_ICON
self.tray = None
self._init_tray()
self.app.new_window_signal.connect(self.start_new_window)
@ -167,7 +167,7 @@ class ElectrumGui(BaseElectrumGui, Logger):
- in Coins tab, the color for "frozen" UTXOs, or
- in TxDialog, the receiving/change address colors
"""
use_dark_theme = self.config.get('qt_gui_color_theme', 'default') == 'dark'
use_dark_theme = self.config.GUI_QT_COLOR_THEME == 'dark'
if use_dark_theme:
try:
import qdarkstyle
@ -219,7 +219,7 @@ class ElectrumGui(BaseElectrumGui, Logger):
if not self.tray:
return
self.dark_icon = not self.dark_icon
self.config.set_key("dark_icon", self.dark_icon, save=True)
self.config.GUI_QT_DARK_TRAY_ICON = self.dark_icon
self.tray.setIcon(self.tray_icon())
def tray_activated(self, reason):
@ -436,7 +436,7 @@ class ElectrumGui(BaseElectrumGui, Logger):
"""Start the network, including showing a first-start network dialog if config does not exist."""
if self.daemon.network:
# first-start network-setup
if self.config.get('auto_connect') is None:
if not self.config.cv.NETWORK_AUTO_CONNECT.is_set():
wizard = InstallWizard(self.config, self.app, self.plugins, gui_object=self)
wizard.init_network(self.daemon.network)
wizard.terminate()

8
electrum/gui/qt/address_list.py

@ -36,6 +36,7 @@ from electrum.util import block_explorer_URL, profiler
from electrum.plugin import run_hook
from electrum.bitcoin import is_address
from electrum.wallet import InternalAddressCorruption
from electrum.simple_config import SimpleConfig
from .util import MONOSPACE_FONT, ColorScheme, webopen
from .my_treeview import MyTreeView, MySortModel
@ -115,23 +116,24 @@ class AddressList(MyTreeView):
self.setModel(self.proxy)
self.update()
self.sortByColumn(self.Columns.TYPE, Qt.AscendingOrder)
if self.config:
self.configvar_show_toolbar = self.config.cv.GUI_QT_ADDRESSES_TAB_SHOW_TOOLBAR
def on_double_click(self, idx):
addr = self.get_role_data_for_current_item(col=0, role=self.ROLE_ADDRESS_STR)
self.main_window.show_address(addr)
CONFIG_KEY_SHOW_TOOLBAR = "show_toolbar_addresses"
def create_toolbar(self, config):
toolbar, menu = self.create_toolbar_with_menu('')
self.num_addr_label = toolbar.itemAt(0).widget()
self._toolbar_checkbox = menu.addToggle(_("Show Filter"), lambda: self.toggle_toolbar())
menu.addConfig(_('Show Fiat balances'), 'fiat_address', False, callback=self.main_window.app.update_fiat_signal.emit)
menu.addConfig(_('Show Fiat balances'), config.cv.FX_SHOW_FIAT_BALANCE_FOR_ADDRESSES, callback=self.main_window.app.update_fiat_signal.emit)
hbox = self.create_toolbar_buttons()
toolbar.insertLayout(1, hbox)
return toolbar
def should_show_fiat(self):
return self.main_window.fx and self.main_window.fx.is_enabled() and self.config.get('fiat_address', False)
return self.main_window.fx and self.main_window.fx.is_enabled() and self.config.FX_SHOW_FIAT_BALANCE_FOR_ADDRESSES
def get_toolbar_buttons(self):
return self.change_button, self.used_button

52
electrum/gui/qt/confirm_tx_dialog.py

@ -102,9 +102,9 @@ class TxEditor(WindowModalDialog):
vbox.addStretch(1)
vbox.addLayout(buttons)
self.set_io_visible(self.config.get('show_tx_io', False))
self.set_fee_edit_visible(self.config.get('show_tx_fee_details', False))
self.set_locktime_visible(self.config.get('show_tx_locktime', False))
self.set_io_visible(self.config.GUI_QT_TX_EDITOR_SHOW_IO)
self.set_fee_edit_visible(self.config.GUI_QT_TX_EDITOR_SHOW_FEE_DETAILS)
self.set_locktime_visible(self.config.GUI_QT_TX_EDITOR_SHOW_LOCKTIME)
self.update_fee_target()
self.resize(self.layout().sizeHint())
@ -127,11 +127,11 @@ class TxEditor(WindowModalDialog):
def set_fee_config(self, dyn, pos, fee_rate):
if dyn:
if self.config.use_mempool_fees():
self.config.set_key('depth_level', pos, save=False)
self.config.cv.FEE_EST_DYNAMIC_MEMPOOL_SLIDERPOS.set(pos, save=False)
else:
self.config.set_key('fee_level', pos, save=False)
self.config.cv.FEE_EST_DYNAMIC_ETA_SLIDERPOS.set(pos, save=False)
else:
self.config.set_key('fee_per_kb', fee_rate, save=False)
self.config.cv.FEE_EST_STATIC_FEERATE_FALLBACK.set(fee_rate, save=False)
def update_tx(self, *, fallback_to_zero_fee: bool = False):
# expected to set self.tx, self.message and self.error
@ -383,15 +383,15 @@ class TxEditor(WindowModalDialog):
m.setToolTip(tooltip)
return m
add_pref_action(
self.config.get('show_tx_io', False),
self.config.GUI_QT_TX_EDITOR_SHOW_IO,
self.toggle_io_visibility,
_('Show inputs and outputs'), '')
add_pref_action(
self.config.get('show_tx_fee_details', False),
self.config.GUI_QT_TX_EDITOR_SHOW_FEE_DETAILS,
self.toggle_fee_details,
_('Edit fees manually'), '')
add_pref_action(
self.config.get('show_tx_locktime', False),
self.config.GUI_QT_TX_EDITOR_SHOW_LOCKTIME,
self.toggle_locktime,
_('Edit Locktime'), '')
self.pref_menu.addSeparator()
@ -410,18 +410,18 @@ class TxEditor(WindowModalDialog):
]))
self.use_multi_change_menu.setEnabled(self.wallet.use_change)
add_pref_action(
self.config.get('batch_rbf', False),
self.config.WALLET_BATCH_RBF,
self.toggle_batch_rbf,
_('Batch unconfirmed transactions'),
_('If you check this box, your unconfirmed transactions will be consolidated into a single transaction.') + '\n' + \
_('This will save fees, but might have unwanted effects in terms of privacy'))
add_pref_action(
self.config.get('confirmed_only', False),
self.config.WALLET_SPEND_CONFIRMED_ONLY,
self.toggle_confirmed_only,
_('Spend only confirmed coins'),
_('Spend only confirmed inputs.'))
add_pref_action(
self.config.get('coin_chooser_output_rounding', True),
self.config.WALLET_COIN_CHOOSER_OUTPUT_ROUNDING,
self.toggle_output_rounding,
_('Enable output value rounding'),
_('Set the value of the change output so that it has similar precision to the other outputs.') + '\n' + \
@ -445,8 +445,8 @@ class TxEditor(WindowModalDialog):
self.resize(size)
def toggle_output_rounding(self):
b = not self.config.get('coin_chooser_output_rounding', True)
self.config.set_key('coin_chooser_output_rounding', b)
b = not self.config.WALLET_COIN_CHOOSER_OUTPUT_ROUNDING
self.config.WALLET_COIN_CHOOSER_OUTPUT_ROUNDING = b
self.trigger_update()
def toggle_use_change(self):
@ -461,30 +461,30 @@ class TxEditor(WindowModalDialog):
self.trigger_update()
def toggle_batch_rbf(self):
b = not self.config.get('batch_rbf', False)
self.config.set_key('batch_rbf', b)
b = not self.config.WALLET_BATCH_RBF
self.config.WALLET_BATCH_RBF = b
self.trigger_update()
def toggle_confirmed_only(self):
b = not self.config.get('confirmed_only', False)
self.config.set_key('confirmed_only', b)
b = not self.config.WALLET_SPEND_CONFIRMED_ONLY
self.config.WALLET_SPEND_CONFIRMED_ONLY = b
self.trigger_update()
def toggle_io_visibility(self):
b = not self.config.get('show_tx_io', False)
self.config.set_key('show_tx_io', b)
b = not self.config.GUI_QT_TX_EDITOR_SHOW_IO
self.config.GUI_QT_TX_EDITOR_SHOW_IO = b
self.set_io_visible(b)
self.resize_to_fit_content()
def toggle_fee_details(self):
b = not self.config.get('show_tx_fee_details', False)
self.config.set_key('show_tx_fee_details', b)
b = not self.config.GUI_QT_TX_EDITOR_SHOW_FEE_DETAILS
self.config.GUI_QT_TX_EDITOR_SHOW_FEE_DETAILS = b
self.set_fee_edit_visible(b)
self.resize_to_fit_content()
def toggle_locktime(self):
b = not self.config.get('show_tx_locktime', False)
self.config.set_key('show_tx_locktime', b)
b = not self.config.GUI_QT_TX_EDITOR_SHOW_LOCKTIME
self.config.GUI_QT_TX_EDITOR_SHOW_LOCKTIME = b
self.set_locktime_visible(b)
self.resize_to_fit_content()
@ -524,7 +524,7 @@ class TxEditor(WindowModalDialog):
self._update_amount_label()
if self.not_enough_funds:
self.error = _('Not enough funds.')
confirmed_only = self.config.get('confirmed_only', False)
confirmed_only = self.config.WALLET_SPEND_CONFIRMED_ONLY
if confirmed_only and self.can_pay_assuming_zero_fees(confirmed_only=False):
self.error += ' ' + _('Change your settings to allow spending unconfirmed coins.')
elif self.can_pay_assuming_zero_fees(confirmed_only=confirmed_only):
@ -631,7 +631,7 @@ class ConfirmTxDialog(TxEditor):
def update_tx(self, *, fallback_to_zero_fee: bool = False):
fee_estimator = self.get_fee_estimator()
confirmed_only = self.config.get('confirmed_only', False)
confirmed_only = self.config.WALLET_SPEND_CONFIRMED_ONLY
try:
self.tx = self.make_tx(fee_estimator, confirmed_only=confirmed_only)
self.not_enough_funds = False

4
electrum/gui/qt/exception_window.py

@ -132,7 +132,7 @@ class Exception_Window(BaseCrashReporter, QWidget, MessageBoxMixin, Logger):
self.close()
def show_never(self):
self.config.set_key(BaseCrashReporter.config_key, False)
self.config.SHOW_CRASH_REPORTER = False
self.close()
def closeEvent(self, event):
@ -177,7 +177,7 @@ class Exception_Hook(QObject, Logger):
@classmethod
def maybe_setup(cls, *, config: 'SimpleConfig', wallet: 'Abstract_Wallet' = None) -> None:
if not config.get(BaseCrashReporter.config_key, default=True):
if not config.SHOW_CRASH_REPORTER:
EarlyExceptionsQueue.set_hook_as_ready() # flush already queued exceptions
return
if not cls._INSTANCE:

4
electrum/gui/qt/fee_slider.py

@ -23,8 +23,8 @@ class FeeComboBox(QComboBox):
)
def on_fee_type(self, x):
self.config.set_key('mempool_fees', x==2)
self.config.set_key('dynamic_fees', x>0)
self.config.FEE_EST_USE_MEMPOOL = (x == 2)
self.config.FEE_EST_DYNAMIC = (x > 0)
self.fee_slider.update()

12
electrum/gui/qt/history_list.py

@ -47,6 +47,7 @@ from electrum.util import (block_explorer_URL, profiler, TxMinedInfo,
OrderedDictWithIndex, timestamp_to_datetime,
Satoshis, Fiat, format_time)
from electrum.logging import get_logger, Logger
from electrum.simple_config import SimpleConfig
from .custom_model import CustomNode, CustomModel
from .util import (read_QIcon, MONOSPACE_FONT, Buttons, CancelButton, OkButton,
@ -252,7 +253,7 @@ class HistoryModel(CustomModel, Logger):
return True
def should_show_fiat(self):
if not bool(self.window.config.get('history_rates', False)):
if not self.window.config.FX_HISTORY_RATES:
return False
fx = self.window.fx
if not fx or not fx.is_enabled():
@ -260,7 +261,7 @@ class HistoryModel(CustomModel, Logger):
return fx.has_history()
def should_show_capital_gains(self):
return self.should_show_fiat() and self.window.config.get('history_rates_capital_gains', False)
return self.should_show_fiat() and self.window.config.FX_HISTORY_RATES_CAPITAL_GAINS
@profiler
def refresh(self, reason: str):
@ -518,6 +519,8 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
for col in HistoryColumns:
sm = QHeaderView.Stretch if col == self.stretch_column else QHeaderView.ResizeToContents
self.header().setSectionResizeMode(col, sm)
if self.config:
self.configvar_show_toolbar = self.config.cv.GUI_QT_HISTORY_TAB_SHOW_TOOLBAR
def update(self):
self.hm.refresh('HistoryList.update()')
@ -546,13 +549,12 @@ class HistoryList(MyTreeView, AcceptFileDragDrop):
self.end_button.setText(_('To') + ' ' + self.format_date(self.end_date))
self.hide_rows()
CONFIG_KEY_SHOW_TOOLBAR = "show_toolbar_history"
def create_toolbar(self, config):
toolbar, menu = self.create_toolbar_with_menu('')
self.num_tx_label = toolbar.itemAt(0).widget()
self._toolbar_checkbox = menu.addToggle(_("Filter by Date"), lambda: self.toggle_toolbar())
self.menu_fiat = menu.addConfig(_('Show Fiat Values'), 'history_rates', False, callback=self.main_window.app.update_fiat_signal.emit)
self.menu_capgains = menu.addConfig(_('Show Capital Gains'), 'history_rates_capital_gains', False, callback=self.main_window.app.update_fiat_signal.emit)
self.menu_fiat = menu.addConfig(_('Show Fiat Values'), config.cv.FX_HISTORY_RATES, callback=self.main_window.app.update_fiat_signal.emit)
self.menu_capgains = menu.addConfig(_('Show Capital Gains'), config.cv.FX_HISTORY_RATES_CAPITAL_GAINS, callback=self.main_window.app.update_fiat_signal.emit)
self.menu_summary = menu.addAction(_("&Summary"), self.show_summary)
menu.addAction(_("&Plot"), self.plot_history_dialog)
menu.addAction(_("&Export"), self.export_history_dialog)

4
electrum/gui/qt/installwizard.py

@ -746,10 +746,10 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
nlayout = NetworkChoiceLayout(network, self.config, wizard=True)
if self.exec_layout(nlayout.layout()):
nlayout.accept()
self.config.set_key('auto_connect', network.auto_connect, save=True)
self.config.NETWORK_AUTO_CONNECT = network.auto_connect
else:
network.auto_connect = True
self.config.set_key('auto_connect', True, save=True)
self.config.NETWORK_AUTO_CONNECT = True
@wizard_dialog
def multisig_dialog(self, run_next):

66
electrum/gui/qt/main_window.py

@ -103,6 +103,7 @@ from .swap_dialog import SwapDialog, InvalidSwapParameters
from .balance_dialog import BalanceToolButton, COLOR_FROZEN, COLOR_UNMATURED, COLOR_UNCONFIRMED, COLOR_CONFIRMED, COLOR_LIGHTNING, COLOR_FROZEN_LIGHTNING
if TYPE_CHECKING:
from electrum.simple_config import ConfigVarWithConfig
from . import ElectrumGui
@ -173,8 +174,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self.gui_thread = gui_object.gui_thread
assert wallet, "no wallet"
self.wallet = wallet
if wallet.has_lightning():
self.wallet.config.set_key('show_channels_tab', True)
if wallet.has_lightning() and not self.config.cv.GUI_QT_SHOW_TAB_CHANNELS.is_set():
self.config.GUI_QT_SHOW_TAB_CHANNELS = True # override default, but still allow disabling tab manually
Exception_Hook.maybe_setup(config=self.config, wallet=self.wallet)
@ -216,19 +217,18 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
tabs.addTab(self.send_tab, read_QIcon("tab_send.png"), _('Send'))
tabs.addTab(self.receive_tab, read_QIcon("tab_receive.png"), _('Receive'))
def add_optional_tab(tabs, tab, icon, description, name):
def add_optional_tab(tabs, tab, icon, description):
tab.tab_icon = icon
tab.tab_description = description
tab.tab_pos = len(tabs)
tab.tab_name = name
if self.config.get('show_{}_tab'.format(name), False):
if tab.is_shown_cv.get():
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.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.contacts_tab, read_QIcon("tab_contacts.png"), _("Con&tacts"), "contacts")
add_optional_tab(tabs, self.console_tab, read_QIcon("tab_console.png"), _("Con&sole"), "console")
add_optional_tab(tabs, self.addresses_tab, read_QIcon("tab_addresses.png"), _("&Addresses"))
add_optional_tab(tabs, self.channels_tab, read_QIcon("lightning.png"), _("Channels"))
add_optional_tab(tabs, self.utxo_tab, read_QIcon("tab_coins.png"), _("Co&ins"))
add_optional_tab(tabs, self.contacts_tab, read_QIcon("tab_contacts.png"), _("Con&tacts"))
add_optional_tab(tabs, self.console_tab, read_QIcon("tab_console.png"), _("Con&sole"))
tabs.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
@ -242,7 +242,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self.setMinimumWidth(640)
self.setMinimumHeight(400)
if self.config.get("is_maximized"):
if self.config.GUI_QT_WINDOW_IS_MAXIMIZED:
self.showMaximized()
self.setWindowIcon(read_QIcon("electrum.png"))
@ -280,14 +280,14 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self.contacts.fetch_openalias(self.config)
# If the option hasn't been set yet
if config.get('check_updates') is None:
if not config.cv.AUTOMATIC_CENTRALIZED_UPDATE_CHECKS.is_set():
choice = self.question(title="Electrum - " + _("Enable update check"),
msg=_("For security reasons we advise that you always use the latest version of Electrum.") + " " +
_("Would you like to be notified when there is a newer version of Electrum available?"))
config.set_key('check_updates', bool(choice), save=True)
config.AUTOMATIC_CENTRALIZED_UPDATE_CHECKS = bool(choice)
self._update_check_thread = None
if config.get('check_updates', False):
if config.AUTOMATIC_CENTRALIZED_UPDATE_CHECKS:
# The references to both the thread and the window need to be stored somewhere
# to prevent GC from getting in our way.
def on_version_received(v):
@ -339,8 +339,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self.address_list.refresh_all()
def toggle_tab(self, tab):
show = not self.config.get('show_{}_tab'.format(tab.tab_name), False)
self.config.set_key('show_{}_tab'.format(tab.tab_name), show)
show = not tab.is_shown_cv.get()
tab.is_shown_cv.set(show)
if show:
# Find out where to place the tab
index = len(self.tabs)
@ -497,7 +497,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self.channels_list.update()
self.tabs.show()
self.init_geometry()
if self.config.get('hide_gui') and self.gui_object.tray.isVisible():
if self.config.GUI_QT_HIDE_ON_STARTUP and self.gui_object.tray.isVisible():
self.hide()
else:
self.show()
@ -552,7 +552,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
if not constants.net.TESTNET:
return
# user might have opted out already
if self.config.get('dont_show_testnet_warning', False):
if self.config.DONT_SHOW_TESTNET_WARNING:
return
# only show once per process lifecycle
if getattr(self.gui_object, '_warned_testnet', False):
@ -571,7 +571,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
cb.stateChanged.connect(on_cb)
self.show_warning(msg, title=_('Testnet'), checkbox=cb)
if cb_checked:
self.config.set_key('dont_show_testnet_warning', True)
self.config.DONT_SHOW_TESTNET_WARNING = True
def open_wallet(self):
try:
@ -585,10 +585,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
self.gui_object.new_window(filename)
def select_backup_dir(self, b):
name = self.config.get('backup_dir', '')
name = self.config.WALLET_BACKUP_DIRECTORY or ""
dirname = QFileDialog.getExistingDirectory(self, "Select your wallet backup directory", name)
if dirname:
self.config.set_key('backup_dir', dirname)
self.config.WALLET_BACKUP_DIRECTORY = dirname
self.backup_dir_e.setText(dirname)
def backup_wallet(self):
@ -596,7 +596,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
vbox = QVBoxLayout(d)
grid = QGridLayout()
backup_help = ""
backup_dir = self.config.get('backup_dir')
backup_dir = self.config.WALLET_BACKUP_DIRECTORY
backup_dir_label = HelpLabel(_('Backup directory') + ':', backup_help)
msg = _('Please select a backup directory')
if self.wallet.has_lightning() and self.wallet.lnworker.channels:
@ -628,7 +628,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
return True
def update_recently_visited(self, filename):
recent = self.config.get('recently_open', [])
recent = self.config.RECENTLY_OPEN_WALLET_FILES or []
try:
sorted(recent)
except Exception:
@ -638,7 +638,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
recent.insert(0, filename)
recent = [path for path in recent if os.path.exists(path)]
recent = recent[:5]
self.config.set_key('recently_open', recent)
self.config.RECENTLY_OPEN_WALLET_FILES = recent
self.recently_visited_menu.clear()
for i, k in enumerate(sorted(recent)):
b = os.path.basename(k)
@ -698,7 +698,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
wallet_menu.addAction(_("Find"), self.toggle_search).setShortcut(QKeySequence("Ctrl+F"))
def add_toggle_action(view_menu, tab):
is_shown = self.config.get('show_{}_tab'.format(tab.tab_name), False)
is_shown = tab.is_shown_cv.get()
tab.menu_action = view_menu.addAction(tab.tab_description, lambda: self.toggle_tab(tab))
tab.menu_action.setCheckable(True)
tab.menu_action.setChecked(is_shown)
@ -1026,7 +1026,9 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
def create_channels_tab(self):
self.channels_list = ChannelsList(self)
return self.create_list_tab(self.channels_list)
tab = self.create_list_tab(self.channels_list)
tab.is_shown_cv = self.config.cv.GUI_QT_SHOW_TAB_CHANNELS
return tab
def create_history_tab(self):
self.history_model = HistoryModel(self)
@ -1351,17 +1353,22 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
from .address_list import AddressList
self.address_list = AddressList(self)
tab = self.create_list_tab(self.address_list)
tab.is_shown_cv = self.config.cv.GUI_QT_SHOW_TAB_ADDRESSES
return tab
def create_utxo_tab(self):
from .utxo_list import UTXOList
self.utxo_list = UTXOList(self)
return self.create_list_tab(self.utxo_list)
tab = self.create_list_tab(self.utxo_list)
tab.is_shown_cv = self.config.cv.GUI_QT_SHOW_TAB_UTXO
return tab
def create_contacts_tab(self):
from .contact_list import ContactList
self.contact_list = l = ContactList(self)
return self.create_list_tab(l)
tab = self.create_list_tab(l)
tab.is_shown_cv = self.config.cv.GUI_QT_SHOW_TAB_CONTACTS
return tab
def remove_address(self, addr):
if not self.question(_("Do you want to remove {} from your wallet?").format(addr)):
@ -1489,6 +1496,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
def create_console_tab(self):
from .console import Console
self.console = console = Console()
console.is_shown_cv = self.config.cv.GUI_QT_SHOW_TAB_CONSOLE
return console
def update_console(self):
@ -2557,7 +2565,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
for fut in coro_keys:
fut.cancel()
self.unregister_callbacks()
self.config.set_key("is_maximized", self.isMaximized())
self.config.GUI_QT_WINDOW_IS_MAXIMIZED = self.isMaximized()
if not self.isMaximized():
g = self.geometry()
self.wallet.db.put("winpos-qt", [g.left(),g.top(),

28
electrum/gui/qt/my_treeview.py

@ -59,6 +59,7 @@ from electrum.util import EventListener, event_listener
from electrum.invoices import PR_UNPAID, PR_PAID, PR_EXPIRED, PR_INFLIGHT, PR_UNKNOWN, PR_FAILED, PR_ROUTING, PR_UNCONFIRMED
from electrum.logging import Logger
from electrum.qrreader import MissingQrDetectionLib
from electrum.simple_config import ConfigVarWithConfig
from .util import read_QIcon
@ -80,17 +81,18 @@ class MyMenu(QMenu):
m.setToolTip(tooltip)
return m
def addConfig(self, text: str, name: str, default: bool, *, tooltip='', callback=None) -> QAction:
b = self.config.get(name, default)
m = self.addAction(text, lambda: self._do_toggle_config(name, default, callback))
def addConfig(self, text: str, configvar: 'ConfigVarWithConfig', *, tooltip='', callback=None) -> QAction:
assert isinstance(configvar, ConfigVarWithConfig), configvar
b = configvar.get()
m = self.addAction(text, lambda: self._do_toggle_config(configvar, callback=callback))
m.setCheckable(True)
m.setChecked(bool(b))
m.setToolTip(tooltip)
return m
def _do_toggle_config(self, name, default, callback):
b = self.config.get(name, default)
self.config.set_key(name, not b)
def _do_toggle_config(self, configvar: 'ConfigVarWithConfig', *, callback):
b = configvar.get()
configvar.set(not b)
if callback:
callback()
@ -387,7 +389,7 @@ class MyTreeView(QTreeView):
for row in range(self.model().rowCount()):
self.hide_row(row)
def create_toolbar(self, config):
def create_toolbar(self, config: 'SimpleConfig'):
return
def create_toolbar_buttons(self):
@ -402,13 +404,13 @@ class MyTreeView(QTreeView):
def create_toolbar_with_menu(self, title):
return create_toolbar_with_menu(self.config, title)
CONFIG_KEY_SHOW_TOOLBAR = None # type: Optional[str]
configvar_show_toolbar = None # type: Optional[ConfigVarWithConfig]
_toolbar_checkbox = None # type: Optional[QAction]
def show_toolbar(self, state: bool = None):
if state is None: # get value from config
if self.config and self.CONFIG_KEY_SHOW_TOOLBAR:
state = self.config.get(self.CONFIG_KEY_SHOW_TOOLBAR, None)
if state is None:
if self.configvar_show_toolbar:
state = self.configvar_show_toolbar.get()
else:
return
assert isinstance(state, bool), state
if state == self.toolbar_shown:
@ -428,8 +430,8 @@ class MyTreeView(QTreeView):
def toggle_toolbar(self):
new_state = not self.toolbar_shown
self.show_toolbar(new_state)
if self.config and self.CONFIG_KEY_SHOW_TOOLBAR:
self.config.set_key(self.CONFIG_KEY_SHOW_TOOLBAR, new_state)
if self.configvar_show_toolbar:
self.configvar_show_toolbar.set(new_state)
def add_copy_menu(self, menu: QMenu, idx) -> QMenu:
cc = menu.addMenu(_("Copy"))

10
electrum/gui/qt/network_dialog.py

@ -41,14 +41,12 @@ from electrum.interface import ServerAddr, PREFERRED_NETWORK_PROTOCOL
from electrum.network import Network
from electrum.logging import get_logger
from electrum.util import detect_tor_socks_proxy
from electrum.simple_config import SimpleConfig
from .util import (Buttons, CloseButton, HelpButton, read_QIcon, char_width_in_lineedit,
PasswordLineEdit)
from .util import QtEventListener, qt_event_listener
if TYPE_CHECKING:
from electrum.simple_config import SimpleConfig
_logger = get_logger(__name__)
@ -279,7 +277,7 @@ class NetworkChoiceLayout(object):
grid.addWidget(HelpButton(msg), 0, 4)
self.autoconnect_cb = QCheckBox(_('Select server automatically'))
self.autoconnect_cb.setEnabled(self.config.is_modifiable('auto_connect'))
self.autoconnect_cb.setEnabled(self.config.cv.NETWORK_AUTO_CONNECT.is_modifiable())
self.autoconnect_cb.clicked.connect(self.set_server)
self.autoconnect_cb.clicked.connect(self.update)
msg = ' '.join([
@ -327,13 +325,13 @@ class NetworkChoiceLayout(object):
self.td = None
def check_disable_proxy(self, b):
if not self.config.is_modifiable('proxy'):
if not self.config.cv.NETWORK_PROXY.is_modifiable():
b = False
for w in [self.proxy_mode, self.proxy_host, self.proxy_port, self.proxy_user, self.proxy_password]:
w.setEnabled(b)
def enable_set_server(self):
if self.config.is_modifiable('server'):
if self.config.cv.NETWORK_SERVER.is_modifiable():
enabled = not self.autoconnect_cb.isChecked()
self.server_e.setEnabled(enabled)
else:

2
electrum/gui/qt/new_channel_dialog.py

@ -37,7 +37,7 @@ class NewChannelDialog(WindowModalDialog):
toolbar, menu = create_toolbar_with_menu(self.config, '')
recov_tooltip = messages.to_rtf(messages.MSG_RECOVERABLE_CHANNELS)
menu.addConfig(
_("Create recoverable channels"), 'use_recoverable_channels', True,
_("Create recoverable channels"), self.config.cv.LIGHTNING_USE_RECOVERABLE_CHANNELS,
tooltip=recov_tooltip,
).setEnabled(self.lnworker.can_have_recoverable_channels())
vbox.addLayout(toolbar)

4
electrum/gui/qt/qrreader/qtmultimedia/camera_dialog.py

@ -130,7 +130,7 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog):
# Flip horizontally checkbox with default coming from global config
self.flip_x = QCheckBox()
self.flip_x.setText(_("&Flip horizontally"))
self.flip_x.setChecked(bool(self.config.get('qrreader_flip_x', True)))
self.flip_x.setChecked(self.config.QR_READER_FLIP_X)
self.flip_x.stateChanged.connect(self._on_flip_x_changed)
controls_layout.addWidget(self.flip_x)
@ -155,7 +155,7 @@ class QrReaderCameraDialog(Logger, MessageBoxMixin, QDialog):
self.finished.connect(self._on_finished, Qt.QueuedConnection)
def _on_flip_x_changed(self, _state: int):
self.config.set_key('qrreader_flip_x', self.flip_x.isChecked())
self.config.QR_READER_FLIP_X = self.flip_x.isChecked()
def _get_resolution(self, resolutions: List[QSize], min_size: int) -> QSize:
"""

26
electrum/gui/qt/receive_tab.py

@ -147,10 +147,10 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
self.toolbar.insertWidget(2, self.toggle_view_button)
# menu
menu.addConfig(
_('Add on-chain fallback to lightning requests'), 'bolt11_fallback', True,
_('Add on-chain fallback to lightning requests'), self.config.cv.WALLET_BOLT11_FALLBACK,
callback=self.on_toggle_bolt11_fallback)
menu.addConfig(
_('Add lightning requests to bitcoin URIs'), 'bip21_lightning', False,
_('Add lightning requests to bitcoin URIs'), self.config.cv.WALLET_BIP21_LIGHTNING,
tooltip=_('This may result in large QR codes'),
callback=self.update_current_request)
self.qr_menu_action = menu.addToggle(_("Show detached QR code window"), self.window.toggle_qr_window)
@ -181,7 +181,7 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
self.update_expiry_text()
def update_expiry_text(self):
expiry = self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
expiry = self.config.WALLET_PAYREQ_EXPIRY_SECONDS
text = pr_expiration_values[expiry]
self.expiry_button.setText(text)
@ -196,11 +196,11 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
'\n\n',
_('For Lightning requests, payments will not be accepted after the expiration.'),
])
expiry = self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
expiry = self.config.WALLET_PAYREQ_EXPIRY_SECONDS
v = self.window.query_choice(msg, pr_expiration_values, title=_('Expiry'), default_choice=expiry)
if v is None:
return
self.config.set_key('request_expiry', v)
self.config.WALLET_PAYREQ_EXPIRY_SECONDS = v
self.update_expiry_text()
def on_toggle_bolt11_fallback(self):
@ -210,7 +210,7 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
self.update_current_request()
def update_view_button(self):
i = self.config.get('receive_tabs_index', 0)
i = self.config.GUI_QT_RECEIVE_TABS_INDEX
if i == 0:
icon, text = read_QIcon("link.png"), _('Bitcoin URI')
elif i == 1:
@ -221,9 +221,9 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
self.toggle_view_button.setIcon(icon)
def toggle_view(self):
i = self.config.get('receive_tabs_index', 0)
i = self.config.GUI_QT_RECEIVE_TABS_INDEX
i = (i + 1) % (3 if self.wallet.has_lightning() else 2)
self.config.set_key('receive_tabs_index', i)
self.config.GUI_QT_RECEIVE_TABS_INDEX = i
self.update_current_request()
self.update_view_button()
@ -239,12 +239,12 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
self.window.do_copy(data, title=title)
def toggle_receive_qr(self):
b = not self.config.get('receive_qr_visible', False)
self.config.set_key('receive_qr_visible', b)
b = not self.config.GUI_QT_RECEIVE_TAB_QR_VISIBLE
self.config.GUI_QT_RECEIVE_TAB_QR_VISIBLE = b
self.update_receive_widgets()
def update_receive_widgets(self):
b = self.config.get('receive_qr_visible', False)
b = self.config.GUI_QT_RECEIVE_TAB_QR_VISIBLE
self.receive_widget.update_visibility(b)
def update_current_request(self):
@ -286,7 +286,7 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
self.update_receive_qr_window()
def get_tab_data(self):
i = self.config.get('receive_tabs_index', 0)
i = self.config.GUI_QT_RECEIVE_TABS_INDEX
if i == 0:
out = self.URI, self.URI, self.URI_help, _('Bitcoin URI')
elif i == 1:
@ -305,7 +305,7 @@ class ReceiveTab(QWidget, MessageBoxMixin, Logger):
def create_invoice(self):
amount_sat = self.receive_amount_e.get_amount()
message = self.receive_message_e.text()
expiry = self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
expiry = self.config.WALLET_PAYREQ_EXPIRY_SECONDS
if amount_sat and amount_sat < self.wallet.dust_threshold():
address = None

75
electrum/gui/qt/settings_dialog.py

@ -72,18 +72,18 @@ class SettingsDialog(QDialog, QtEventListener):
lang_combo = QComboBox()
lang_combo.addItems(list(languages.values()))
lang_keys = list(languages.keys())
lang_cur_setting = self.config.get("language", '')
lang_cur_setting = self.config.LOCALIZATION_LANGUAGE
try:
index = lang_keys.index(lang_cur_setting)
except ValueError: # not in list
index = 0
lang_combo.setCurrentIndex(index)
if not self.config.is_modifiable('language'):
if not self.config.cv.LOCALIZATION_LANGUAGE.is_modifiable():
for w in [lang_combo, lang_label]: w.setEnabled(False)
def on_lang(x):
lang_request = list(languages.keys())[lang_combo.currentIndex()]
if lang_request != self.config.get('language'):
self.config.set_key("language", lang_request, save=True)
if lang_request != self.config.LOCALIZATION_LANGUAGE:
self.config.LOCALIZATION_LANGUAGE = lang_request
self.need_restart = True
lang_combo.currentIndexChanged.connect(on_lang)
@ -93,13 +93,13 @@ class SettingsDialog(QDialog, QtEventListener):
nz.setMinimum(0)
nz.setMaximum(self.config.decimal_point)
nz.setValue(self.config.num_zeros)
if not self.config.is_modifiable('num_zeros'):
if not self.config.cv.BTC_AMOUNTS_FORCE_NZEROS_AFTER_DECIMAL_POINT.is_modifiable():
for w in [nz, nz_label]: w.setEnabled(False)
def on_nz():
value = nz.value()
if self.config.num_zeros != value:
self.config.num_zeros = value
self.config.set_key('num_zeros', value, save=True)
self.config.BTC_AMOUNTS_FORCE_NZEROS_AFTER_DECIMAL_POINT = value
self.app.refresh_tabs_signal.emit()
self.app.update_status_signal.emit()
nz.valueChanged.connect(on_nz)
@ -108,7 +108,7 @@ class SettingsDialog(QDialog, QtEventListener):
help_trampoline = messages.MSG_HELP_TRAMPOLINE
trampoline_cb = QCheckBox(_("Use trampoline routing"))
trampoline_cb.setToolTip(messages.to_rtf(help_trampoline))
trampoline_cb.setChecked(not bool(self.config.get('use_gossip', False)))
trampoline_cb.setChecked(not self.config.LIGHTNING_USE_GOSSIP)
def on_trampoline_checked(use_trampoline):
use_trampoline = bool(use_trampoline)
if not use_trampoline:
@ -119,7 +119,7 @@ class SettingsDialog(QDialog, QtEventListener):
])):
trampoline_cb.setCheckState(Qt.Checked)
return
self.config.set_key('use_gossip', not use_trampoline)
self.config.LIGHTNING_USE_GOSSIP = not use_trampoline
if not use_trampoline:
self.network.start_gossip()
else:
@ -137,17 +137,17 @@ class SettingsDialog(QDialog, QtEventListener):
])
remote_wt_cb = QCheckBox(_("Use a remote watchtower"))
remote_wt_cb.setToolTip('<p>'+help_remote_wt+'</p>')
remote_wt_cb.setChecked(bool(self.config.get('use_watchtower', False)))
remote_wt_cb.setChecked(self.config.WATCHTOWER_CLIENT_ENABLED)
def on_remote_wt_checked(x):
self.config.set_key('use_watchtower', bool(x))
self.config.WATCHTOWER_CLIENT_ENABLED = bool(x)
self.watchtower_url_e.setEnabled(bool(x))
remote_wt_cb.stateChanged.connect(on_remote_wt_checked)
watchtower_url = self.config.get('watchtower_url')
watchtower_url = self.config.WATCHTOWER_CLIENT_URL
self.watchtower_url_e = QLineEdit(watchtower_url)
self.watchtower_url_e.setEnabled(self.config.get('use_watchtower', False))
self.watchtower_url_e.setEnabled(self.config.WATCHTOWER_CLIENT_ENABLED)
def on_wt_url():
url = self.watchtower_url_e.text() or None
watchtower_url = self.config.set_key('watchtower_url', url)
self.config.WATCHTOWER_CLIENT_URL = url
self.watchtower_url_e.editingFinished.connect(on_wt_url)
msg = _('OpenAlias record, used to receive coins and to sign payment requests.') + '\n\n'\
@ -155,18 +155,18 @@ class SettingsDialog(QDialog, QtEventListener):
+ '\n'.join(['https://cryptoname.co/', 'http://xmr.link']) + '\n\n'\
+ 'For more information, see https://openalias.org'
alias_label = HelpLabel(_('OpenAlias') + ':', msg)
alias = self.config.get('alias','')
alias = self.config.OPENALIAS_ID
self.alias_e = QLineEdit(alias)
self.set_alias_color()
self.alias_e.editingFinished.connect(self.on_alias_edit)
msat_cb = QCheckBox(_("Show Lightning amounts with msat precision"))
msat_cb.setChecked(bool(self.config.get('amt_precision_post_satoshi', False)))
msat_cb.setChecked(self.config.BTC_AMOUNTS_PREC_POST_SAT > 0)
def on_msat_checked(v):
prec = 3 if v == Qt.Checked else 0
if self.config.amt_precision_post_satoshi != prec:
self.config.amt_precision_post_satoshi = prec
self.config.set_key('amt_precision_post_satoshi', prec)
self.config.BTC_AMOUNTS_PREC_POST_SAT = prec
self.app.refresh_tabs_signal.emit()
msat_cb.stateChanged.connect(on_msat_checked)
@ -191,12 +191,12 @@ class SettingsDialog(QDialog, QtEventListener):
unit_combo.currentIndexChanged.connect(lambda x: on_unit(x, nz))
thousandsep_cb = QCheckBox(_("Add thousand separators to bitcoin amounts"))
thousandsep_cb.setChecked(bool(self.config.get('amt_add_thousands_sep', False)))
thousandsep_cb.setChecked(self.config.BTC_AMOUNTS_ADD_THOUSANDS_SEP)
def on_set_thousandsep(v):
checked = v == Qt.Checked
if self.config.amt_add_thousands_sep != checked:
self.config.amt_add_thousands_sep = checked
self.config.set_key('amt_add_thousands_sep', checked)
self.config.BTC_AMOUNTS_ADD_THOUSANDS_SEP = checked
self.app.refresh_tabs_signal.emit()
thousandsep_cb.stateChanged.connect(on_set_thousandsep)
@ -209,32 +209,33 @@ class SettingsDialog(QDialog, QtEventListener):
system_cameras = find_system_cameras()
for cam_desc, cam_path in system_cameras.items():
qr_combo.addItem(cam_desc, cam_path)
index = qr_combo.findData(self.config.get("video_device"))
index = qr_combo.findData(self.config.VIDEO_DEVICE_PATH)
qr_combo.setCurrentIndex(index)
on_video_device = lambda x: self.config.set_key("video_device", qr_combo.itemData(x), save=True)
def on_video_device(x):
self.config.VIDEO_DEVICE_PATH = qr_combo.itemData(x)
qr_combo.currentIndexChanged.connect(on_video_device)
colortheme_combo = QComboBox()
colortheme_combo.addItem(_('Light'), 'default')
colortheme_combo.addItem(_('Dark'), 'dark')
index = colortheme_combo.findData(self.config.get('qt_gui_color_theme', 'default'))
index = colortheme_combo.findData(self.config.GUI_QT_COLOR_THEME)
colortheme_combo.setCurrentIndex(index)
colortheme_label = QLabel(_('Color theme') + ':')
def on_colortheme(x):
self.config.set_key('qt_gui_color_theme', colortheme_combo.itemData(x), save=True)
self.config.GUI_QT_COLOR_THEME = colortheme_combo.itemData(x)
self.need_restart = True
colortheme_combo.currentIndexChanged.connect(on_colortheme)
updatecheck_cb = QCheckBox(_("Automatically check for software updates"))
updatecheck_cb.setChecked(bool(self.config.get('check_updates', False)))
updatecheck_cb.setChecked(self.config.AUTOMATIC_CENTRALIZED_UPDATE_CHECKS)
def on_set_updatecheck(v):
self.config.set_key('check_updates', v == Qt.Checked, save=True)
self.config.AUTOMATIC_CENTRALIZED_UPDATE_CHECKS = (v == Qt.Checked)
updatecheck_cb.stateChanged.connect(on_set_updatecheck)
filelogging_cb = QCheckBox(_("Write logs to file"))
filelogging_cb.setChecked(bool(self.config.get('log_to_file', False)))
filelogging_cb.setChecked(self.config.WRITE_LOGS_TO_DISK)
def on_set_filelogging(v):
self.config.set_key('log_to_file', v == Qt.Checked, save=True)
self.config.WRITE_LOGS_TO_DISK = (v == Qt.Checked)
self.need_restart = True
filelogging_cb.stateChanged.connect(on_set_filelogging)
filelogging_cb.setToolTip(_('Debug logs can be persisted to disk. These are useful for troubleshooting.'))
@ -256,7 +257,7 @@ class SettingsDialog(QDialog, QtEventListener):
chooser_combo.setCurrentIndex(i)
def on_chooser(x):
chooser_name = choosers[chooser_combo.currentIndex()]
self.config.set_key('coin_chooser', chooser_name)
self.config.WALLET_COIN_CHOOSER_POLICY = chooser_name
chooser_combo.currentIndexChanged.connect(on_chooser)
block_explorers = sorted(util.block_explorer_info().keys())
@ -267,7 +268,7 @@ class SettingsDialog(QDialog, QtEventListener):
msg = _('Choose which online block explorer to use for functions that open a web browser')
block_ex_label = HelpLabel(_('Online Block Explorer') + ':', msg)
block_ex_combo = QComboBox()
block_ex_custom_e = QLineEdit(str(self.config.get('block_explorer_custom') or ''))
block_ex_custom_e = QLineEdit(str(self.config.BLOCK_EXPLORER_CUSTOM or ''))
block_ex_combo.addItems(block_explorers)
block_ex_combo.setCurrentIndex(
block_ex_combo.findText(util.block_explorer(self.config) or BLOCK_EX_CUSTOM_ITEM))
@ -279,8 +280,8 @@ class SettingsDialog(QDialog, QtEventListener):
on_be_edit()
else:
be_result = block_explorers[block_ex_combo.currentIndex()]
self.config.set_key('block_explorer_custom', None, save=False)
self.config.set_key('block_explorer', be_result, save=True)
self.config.BLOCK_EXPLORER_CUSTOM = None
self.config.BLOCK_EXPLORER = be_result
showhide_block_ex_custom_e()
block_ex_combo.currentIndexChanged.connect(on_be_combo)
def on_be_edit():
@ -289,7 +290,7 @@ class SettingsDialog(QDialog, QtEventListener):
val = ast.literal_eval(val) # to also accept tuples
except Exception:
pass
self.config.set_key('block_explorer_custom', val)
self.config.BLOCK_EXPLORER_CUSTOM = val
block_ex_custom_e.editingFinished.connect(on_be_edit)
block_ex_hbox = QHBoxLayout()
block_ex_hbox.setContentsMargins(0, 0, 0, 0)
@ -307,7 +308,7 @@ class SettingsDialog(QDialog, QtEventListener):
def update_currencies():
if not self.fx:
return
h = bool(self.config.get('history_rates', False))
h = self.config.FX_HISTORY_RATES
currencies = sorted(self.fx.get_currencies(h))
ccy_combo.clear()
ccy_combo.addItems([_('None')] + currencies)
@ -319,7 +320,7 @@ class SettingsDialog(QDialog, QtEventListener):
b = self.fx.is_enabled()
ex_combo.setEnabled(b)
if b:
h = bool(self.config.get('history_rates', False))
h = self.config.FX_HISTORY_RATES
c = self.fx.get_currency()
exchanges = self.fx.get_exchanges_by_ccy(c, h)
else:
@ -347,7 +348,7 @@ class SettingsDialog(QDialog, QtEventListener):
self.app.update_fiat_signal.emit()
def on_history_rates(checked):
self.config.set_key('history_rates', bool(checked))
self.config.FX_HISTORY_RATES = bool(checked)
if not self.fx:
return
update_exchanges()
@ -356,7 +357,7 @@ class SettingsDialog(QDialog, QtEventListener):
update_currencies()
update_exchanges()
ccy_combo.currentIndexChanged.connect(on_currency)
self.history_rates_cb.setChecked(bool(self.config.get('history_rates', False)))
self.history_rates_cb.setChecked(self.config.FX_HISTORY_RATES)
self.history_rates_cb.stateChanged.connect(on_history_rates)
ex_combo.currentIndexChanged.connect(on_exchange)
@ -417,7 +418,7 @@ class SettingsDialog(QDialog, QtEventListener):
self.app.alias_received_signal.emit()
def set_alias_color(self):
if not self.config.get('alias'):
if not self.config.OPENALIAS_ID:
self.alias_e.setStyleSheet("")
return
if self.wallet.contacts.alias_info:
@ -429,7 +430,7 @@ class SettingsDialog(QDialog, QtEventListener):
def on_alias_edit(self):
self.alias_e.setStyleSheet("")
alias = str(self.alias_e.text())
self.config.set_key('alias', alias, save=True)
self.config.OPENALIAS_ID = alias
if alias:
self.wallet.contacts.fetch_openalias(self.config)

8
electrum/gui/qt/swap_dialog.py

@ -45,7 +45,7 @@ class SwapDialog(WindowModalDialog, QtEventListener):
vbox = QVBoxLayout(self)
toolbar, menu = create_toolbar_with_menu(self.config, '')
menu.addConfig(
_("Allow instant swaps"), 'allow_instant_swaps', False,
_("Allow instant swaps"), self.config.cv.LIGHTNING_ALLOW_INSTANT_SWAPS,
tooltip=messages.to_rtf(messages.MSG_CONFIG_INSTANT_SWAPS),
).setEnabled(self.lnworker.can_have_recoverable_channels())
vbox.addLayout(toolbar)
@ -138,11 +138,11 @@ class SwapDialog(WindowModalDialog, QtEventListener):
def fee_slider_callback(self, dyn, pos, fee_rate):
if dyn:
if self.config.use_mempool_fees():
self.config.set_key('depth_level', pos, save=False)
self.config.cv.FEE_EST_DYNAMIC_MEMPOOL_SLIDERPOS.set(pos, save=False)
else:
self.config.set_key('fee_level', pos, save=False)
self.config.cv.FEE_EST_DYNAMIC_ETA_SLIDERPOS.set(pos, save=False)
else:
self.config.set_key('fee_per_kb', fee_rate, save=False)
self.config.cv.FEE_EST_STATIC_FEERATE_FALLBACK.set(fee_rate, save=False)
if self.send_follows:
self.on_recv_edited()
else:

4
electrum/gui/qt/transaction_dialog.py

@ -416,7 +416,7 @@ class TxDialog(QDialog, MessageBoxMixin):
self.setLayout(vbox)
toolbar, menu = create_toolbar_with_menu(self.config, '')
menu.addConfig(
_('Download missing data'), 'tx_dialog_fetch_txin_data', False,
_('Download missing data'), self.config.cv.GUI_QT_TX_DIALOG_FETCH_TXIN_DATA,
tooltip=_(
'Download parent transactions from the network.\n'
'Allows filling in missing fee and input details.'),
@ -945,7 +945,7 @@ class TxDialog(QDialog, MessageBoxMixin):
We could also SPV-verify the tx, to fill in missing tx_mined_status (block height, blockhash, timestamp),
but this is not done currently.
"""
if not self.config.get('tx_dialog_fetch_txin_data', False):
if not self.config.GUI_QT_TX_DIALOG_FETCH_TXIN_DATA:
return
tx = self.tx
if not tx:

10
electrum/gui/qt/util.py

@ -483,7 +483,7 @@ def filename_field(parent, config, defaultname, select_msg):
hbox = QHBoxLayout()
directory = config.get('io_dir', os.path.expanduser('~'))
directory = config.IO_DIRECTORY
path = os.path.join(directory, defaultname)
filename_e = QLineEdit()
filename_e.setText(path)
@ -1048,10 +1048,10 @@ def export_meta_gui(electrum_window: 'ElectrumWindow', title, exporter):
def getOpenFileName(*, parent, title, filter="", config: 'SimpleConfig') -> Optional[str]:
"""Custom wrapper for getOpenFileName that remembers the path selected by the user."""
directory = config.get('io_dir', os.path.expanduser('~'))
directory = config.IO_DIRECTORY
fileName, __ = QFileDialog.getOpenFileName(parent, title, directory, filter)
if fileName and directory != os.path.dirname(fileName):
config.set_key('io_dir', os.path.dirname(fileName), save=True)
config.IO_DIRECTORY = os.path.dirname(fileName)
return fileName
@ -1066,7 +1066,7 @@ def getSaveFileName(
config: 'SimpleConfig',
) -> Optional[str]:
"""Custom wrapper for getSaveFileName that remembers the path selected by the user."""
directory = config.get('io_dir', os.path.expanduser('~'))
directory = config.IO_DIRECTORY
path = os.path.join(directory, filename)
file_dialog = QFileDialog(parent, title, path, filter)
@ -1082,7 +1082,7 @@ def getSaveFileName(
selected_path = file_dialog.selectedFiles()[0]
if selected_path and directory != os.path.dirname(selected_path):
config.set_key('io_dir', os.path.dirname(selected_path), save=True)
config.IO_DIRECTORY = os.path.dirname(selected_path)
return selected_path

6
electrum/gui/text.py

@ -558,7 +558,7 @@ class ElectrumGui(BaseElectrumGui, EventListener):
if not address:
return
message = self.str_recv_description
expiry = self.config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
expiry = self.config.WALLET_PAYREQ_EXPIRY_SECONDS
key = self.wallet.create_request(amount_sat, message, expiry, address)
self.do_clear_request()
self.pos = self.max_pos
@ -719,7 +719,7 @@ class ElectrumGui(BaseElectrumGui, EventListener):
srv = 'auto-connect' if auto_connect else str(self.network.default_server)
out = self.run_dialog('Network', [
{'label':'server', 'type':'str', 'value':srv},
{'label':'proxy', 'type':'str', 'value':self.config.get('proxy', '')},
{'label':'proxy', 'type':'str', 'value':self.config.NETWORK_PROXY},
], buttons = 1)
if out:
if out.get('server'):
@ -747,7 +747,7 @@ class ElectrumGui(BaseElectrumGui, EventListener):
if out:
if out.get('Default fee'):
fee = int(Decimal(out['Default fee']) * COIN)
self.config.set_key('fee_per_kb', fee, save=True)
self.config.FEE_EST_STATIC_FEERATE_FALLBACK = fee
def password_dialog(self):
out = self.run_dialog('Password', [

8
electrum/interface.py

@ -68,8 +68,6 @@ ca_path = certifi.where()
BUCKET_NAME_OF_ONION_SERVERS = 'onion'
MAX_INCOMING_MSG_SIZE = 1_000_000 # in bytes
_KNOWN_NETWORK_PROTOCOLS = {'t', 's'}
PREFERRED_NETWORK_PROTOCOL = 's'
assert PREFERRED_NETWORK_PROTOCOL in _KNOWN_NETWORK_PROTOCOLS
@ -216,8 +214,8 @@ class NotificationSession(RPCSession):
def default_framer(self):
# overridden so that max_size can be customized
max_size = int(self.interface.network.config.get('network_max_incoming_msg_size',
MAX_INCOMING_MSG_SIZE))
max_size = self.interface.network.config.NETWORK_MAX_INCOMING_MSG_SIZE
assert max_size > 500_000, f"{max_size=} (< 500_000) is too small"
return NewlineFramer(max_size=max_size)
async def close(self, *, force_after: int = None):
@ -604,7 +602,7 @@ class Interface(Logger):
def _get_expected_fingerprint(self) -> Optional[str]:
if self.is_main_server():
return self.network.config.get("serverfingerprint")
return self.network.config.NETWORK_SERVERFINGERPRINT
def _verify_certificate_fingerprint(self, certificate):
expected_fingerprint = self._get_expected_fingerprint()

26
electrum/lnpeer.py

@ -647,7 +647,7 @@ class Peer(Logger):
channel_seed=channel_seed,
static_remotekey=static_remotekey,
upfront_shutdown_script=upfront_shutdown_script,
to_self_delay=self.network.config.get('lightning_to_self_delay', 7 * 144),
to_self_delay=self.network.config.LIGHTNING_TO_SELF_DELAY_CSV,
dust_limit_sat=dust_limit_sat,
max_htlc_value_in_flight_msat=funding_sat * 1000,
max_accepted_htlcs=30,
@ -1389,7 +1389,7 @@ class Peer(Logger):
if pending_channel_update:
chan.set_remote_update(pending_channel_update)
self.logger.info(f"CHANNEL OPENING COMPLETED ({chan.get_id_for_log()})")
forwarding_enabled = self.network.config.get('lightning_forward_payments', False)
forwarding_enabled = self.network.config.EXPERIMENTAL_LN_FORWARD_PAYMENTS
if forwarding_enabled:
# send channel_update of outgoing edge to peer,
# so that channel can be used to to receive payments
@ -1578,7 +1578,7 @@ class Peer(Logger):
# (same for trampoline forwarding)
# - we could check for the exposure to dust HTLCs, see:
# https://github.com/ACINQ/eclair/pull/1985
forwarding_enabled = self.network.config.get('lightning_forward_payments', False)
forwarding_enabled = self.network.config.EXPERIMENTAL_LN_FORWARD_PAYMENTS
if not forwarding_enabled:
self.logger.info(f"forwarding is disabled. failing htlc.")
raise OnionRoutingFailure(code=OnionFailureCode.PERMANENT_CHANNEL_FAILURE, data=b'')
@ -1660,8 +1660,8 @@ class Peer(Logger):
htlc: UpdateAddHtlc,
trampoline_onion: ProcessedOnionPacket):
forwarding_enabled = self.network.config.get('lightning_forward_payments', False)
forwarding_trampoline_enabled = self.network.config.get('lightning_forward_trampoline_payments', False)
forwarding_enabled = self.network.config.EXPERIMENTAL_LN_FORWARD_PAYMENTS
forwarding_trampoline_enabled = self.network.config.EXPERIMENTAL_LN_FORWARD_TRAMPOLINE_PAYMENTS
if not (forwarding_enabled and forwarding_trampoline_enabled):
self.logger.info(f"trampoline forwarding is disabled. failing htlc.")
raise OnionRoutingFailure(code=OnionFailureCode.PERMANENT_CHANNEL_FAILURE, data=b'')
@ -1996,8 +1996,8 @@ class Peer(Logger):
""" return the closing fee and fee range we initially try to enforce """
config = self.network.config
our_fee = None
if config.get('test_shutdown_fee'):
our_fee = config.get('test_shutdown_fee')
if config.TEST_SHUTDOWN_FEE:
our_fee = config.TEST_SHUTDOWN_FEE
else:
fee_rate_per_kb = config.eta_target_to_fee(FEE_LN_ETA_TARGET)
if fee_rate_per_kb is None: # fallback
@ -2012,10 +2012,10 @@ class Peer(Logger):
our_fee = max_fee
our_fee = min(our_fee, max_fee)
# config modern_fee_negotiation can be set in tests
if config.get('test_shutdown_legacy'):
if config.TEST_SHUTDOWN_LEGACY:
our_fee_range = None
elif config.get('test_shutdown_fee_range'):
our_fee_range = config.get('test_shutdown_fee_range')
elif config.TEST_SHUTDOWN_FEE_RANGE:
our_fee_range = config.TEST_SHUTDOWN_FEE_RANGE
else:
# we aim at a fee between next block inclusion and some lower value
our_fee_range = {'min_fee_satoshis': our_fee // 2, 'max_fee_satoshis': our_fee * 2}
@ -2101,7 +2101,7 @@ class Peer(Logger):
fee_range_sent = our_fee_range and (is_initiator or (their_previous_fee is not None))
# The sending node, if it is not the funder:
if our_fee_range and their_fee_range and not is_initiator and not self.network.config.get('test_shutdown_fee_range'):
if our_fee_range and their_fee_range and not is_initiator and not self.network.config.TEST_SHUTDOWN_FEE_RANGE:
# SHOULD set max_fee_satoshis to at least the max_fee_satoshis received
our_fee_range['max_fee_satoshis'] = max(their_fee_range['max_fee_satoshis'], our_fee_range['max_fee_satoshis'])
# SHOULD set min_fee_satoshis to a fairly low value
@ -2400,8 +2400,8 @@ class Peer(Logger):
except Exception as e:
self.logger.info(f"error processing onion packet: {e!r}")
raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_VERSION, data=failure_data)
if self.network.config.get('test_fail_malformed_htlc'):
if self.network.config.TEST_FAIL_HTLCS_AS_MALFORMED:
raise OnionRoutingFailure(code=OnionFailureCode.INVALID_ONION_VERSION, data=failure_data)
if self.network.config.get('test_fail_htlcs_with_temp_node_failure'):
if self.network.config.TEST_FAIL_HTLCS_WITH_TEMP_NODE_FAILURE:
raise OnionRoutingFailure(code=OnionFailureCode.TEMPORARY_NODE_FAILURE, data=b'')
return processed_onion

12
electrum/lnworker.py

@ -253,13 +253,13 @@ class LNWorker(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
async def maybe_listen(self):
# FIXME: only one LNWorker can listen at a time (single port)
listen_addr = self.config.get('lightning_listen')
listen_addr = self.config.LIGHTNING_LISTEN
if listen_addr:
self.logger.info(f'lightning_listen enabled. will try to bind: {listen_addr!r}')
try:
netaddr = NetAddress.from_string(listen_addr)
except Exception as e:
self.logger.error(f"failed to parse config key 'lightning_listen'. got: {e!r}")
self.logger.error(f"failed to parse config key '{self.config.cv.LIGHTNING_LISTEN.key()}'. got: {e!r}")
return
addr = str(netaddr.host)
async def cb(reader, writer):
@ -351,7 +351,7 @@ class LNWorker(Logger, EventListener, NetworkRetryManager[LNPeerAddr]):
await self.taskgroup.cancel_remaining()
def _add_peers_from_config(self):
peer_list = self.config.get('lightning_peers', [])
peer_list = self.config.LIGHTNING_PEERS or []
for host, port, pubkey in peer_list:
asyncio.run_coroutine_threadsafe(
self._add_peer(host, int(port), bfh(pubkey)),
@ -675,14 +675,14 @@ class LNWallet(LNWorker):
def can_have_recoverable_channels(self) -> bool:
return (self.has_deterministic_node_id()
and not (self.config.get('lightning_listen')))
and not self.config.LIGHTNING_LISTEN)
def has_recoverable_channels(self) -> bool:
"""Whether *future* channels opened by this wallet would be recoverable
from seed (via putting OP_RETURN outputs into funding txs).
"""
return (self.can_have_recoverable_channels()
and self.config.get('use_recoverable_channels', True))
and self.config.LIGHTNING_USE_RECOVERABLE_CHANNELS)
@property
def channels(self) -> Mapping[bytes, Channel]:
@ -728,7 +728,7 @@ class LNWallet(LNWorker):
while True:
# periodically poll if the user updated 'watchtower_url'
await asyncio.sleep(5)
watchtower_url = self.config.get('watchtower_url')
watchtower_url = self.config.WATCHTOWER_CLIENT_URL
if not watchtower_url:
continue
parsed_url = urllib.parse.urlparse(watchtower_url)

4
electrum/logging.py

@ -318,12 +318,12 @@ def configure_logging(config: 'SimpleConfig', *, log_to_file: Optional[bool] = N
verbosity = config.get('verbosity')
verbosity_shortcuts = config.get('verbosity_shortcuts')
if not verbosity and config.get('gui_enable_debug_logs'):
if not verbosity and config.GUI_ENABLE_DEBUG_LOGS:
verbosity = '*'
_configure_stderr_logging(verbosity=verbosity, verbosity_shortcuts=verbosity_shortcuts)
if log_to_file is None:
log_to_file = config.get('log_to_file', False)
log_to_file = config.WRITE_LOGS_TO_DISK
log_to_file |= is_android_debug_apk()
if log_to_file:
log_directory = pathlib.Path(config.path) / "logs"

38
electrum/network.py

@ -172,7 +172,7 @@ def serialize_proxy(p):
p.get('user', ''), p.get('password', '')])
def deserialize_proxy(s: str) -> Optional[dict]:
def deserialize_proxy(s: Optional[str]) -> Optional[dict]:
if not isinstance(s, str):
return None
if s.lower() == 'none':
@ -295,7 +295,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
blockchain.read_blockchains(self.config)
blockchain.init_headers_file_for_best_chain()
self.logger.info(f"blockchains {list(map(lambda b: b.forkpoint, blockchain.blockchains.values()))}")
self._blockchain_preferred_block = self.config.get('blockchain_preferred_block', None) # type: Dict[str, Any]
self._blockchain_preferred_block = self.config.BLOCKCHAIN_PREFERRED_BLOCK # type: Dict[str, Any]
if self._blockchain_preferred_block is None:
self._set_preferred_chain(None)
self._blockchain = blockchain.get_best_chain()
@ -342,7 +342,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
self._was_started = False
# lightning network
if self.config.get('run_watchtower', False):
if self.config.WATCHTOWER_SERVER_ENABLED:
from . import lnwatcher
self.local_watchtower = lnwatcher.WatchTower(self)
asyncio.ensure_future(self.local_watchtower.start_watching())
@ -358,7 +358,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
from . import lnrouter
from . import channel_db
from . import lnworker
if not self.config.get('use_gossip'):
if not self.config.LIGHTNING_USE_GOSSIP:
return
if self.lngossip is None:
self.channel_db = channel_db.ChannelDB(self)
@ -489,9 +489,9 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
oneserver=self.oneserver)
def _init_parameters_from_config(self) -> None:
self.auto_connect = self.config.get('auto_connect', True)
self.auto_connect = self.config.NETWORK_AUTO_CONNECT
self._set_default_server()
self._set_proxy(deserialize_proxy(self.config.get('proxy')))
self._set_proxy(deserialize_proxy(self.config.NETWORK_PROXY))
self._maybe_set_oneserver()
def get_donation_address(self):
@ -554,7 +554,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
else:
out[server.host] = {server.protocol: port}
# potentially filter out some
if self.config.get('noonion'):
if self.config.NETWORK_NOONION:
out = filter_noonion(out)
return out
@ -590,7 +590,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
def _set_default_server(self) -> None:
# Server for addresses and transactions
server = self.config.get('server', None)
server = self.config.NETWORK_SERVER
# Sanitize default server
if server:
try:
@ -628,14 +628,14 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
int(proxy['port'])
except Exception:
return
self.config.set_key('auto_connect', net_params.auto_connect, save=False)
self.config.set_key('oneserver', net_params.oneserver, save=False)
self.config.set_key('proxy', proxy_str, save=False)
self.config.set_key('server', str(server), save=True)
self.config.NETWORK_AUTO_CONNECT = net_params.auto_connect
self.config.NETWORK_ONESERVER = net_params.oneserver
self.config.NETWORK_PROXY = proxy_str
self.config.NETWORK_SERVER = str(server)
# abort if changes were not allowed by config
if self.config.get('server') != str(server) \
or self.config.get('proxy') != proxy_str \
or self.config.get('oneserver') != net_params.oneserver:
if self.config.NETWORK_SERVER != str(server) \
or self.config.NETWORK_PROXY != proxy_str \
or self.config.NETWORK_ONESERVER != net_params.oneserver:
return
proxy_changed = self.proxy != proxy
@ -657,7 +657,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
util.trigger_callback('network_updated')
def _maybe_set_oneserver(self) -> None:
oneserver = bool(self.config.get('oneserver', False))
oneserver = self.config.NETWORK_ONESERVER
self.oneserver = oneserver
self.num_server = NUM_TARGET_CONNECTED_SERVERS if not oneserver else 0
@ -788,8 +788,8 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
util.trigger_callback('network_updated')
def get_network_timeout_seconds(self, request_type=NetworkTimeout.Generic) -> int:
if self.config.get('network_timeout', None):
return int(self.config.get('network_timeout'))
if self.config.NETWORK_TIMEOUT:
return self.config.NETWORK_TIMEOUT
if self.oneserver and not self.auto_connect:
return request_type.MOST_RELAXED
if self.proxy:
@ -1191,7 +1191,7 @@ class Network(Logger, NetworkRetryManager[ServerAddr]):
'height': height,
'hash': header_hash,
}
self.config.set_key('blockchain_preferred_block', self._blockchain_preferred_block)
self.config.BLOCKCHAIN_PREFERRED_BLOCK = self._blockchain_preferred_block
async def follow_chain_given_id(self, chain_id: str) -> None:
bc = blockchain.blockchains.get(chain_id)

10
electrum/paymentrequest.py

@ -400,10 +400,10 @@ def verify_cert_chain(chain):
return x509_chain[0], ca
def check_ssl_config(config):
def check_ssl_config(config: 'SimpleConfig'):
from . import pem
key_path = config.get('ssl_keyfile')
cert_path = config.get('ssl_certfile')
key_path = config.SSL_KEYFILE_PATH
cert_path = config.SSL_CERTFILE_PATH
with open(key_path, 'r', encoding='utf-8') as f:
params = pem.parse_private_key(f.read())
with open(cert_path, 'r', encoding='utf-8') as f:
@ -453,8 +453,8 @@ def serialize_request(req): # FIXME this is broken
def make_request(config: 'SimpleConfig', req: 'Invoice'):
pr = make_unsigned_request(req)
key_path = config.get('ssl_keyfile')
cert_path = config.get('ssl_certfile')
key_path = config.SSL_KEYFILE_PATH
cert_path = config.SSL_CERTFILE_PATH
if key_path and cert_path:
sign_request_with_x509(pr, key_path, cert_path)
return pr

1
electrum/plugins/ledger/ledger.py

@ -1344,7 +1344,6 @@ class LedgerPlugin(HW_PluginBase):
SUPPORTED_XTYPES = ('standard', 'p2wpkh-p2sh', 'p2wpkh', 'p2wsh-p2sh', 'p2wsh')
def __init__(self, parent, config, name):
self.segwit = config.get("segwit")
HW_PluginBase.__init__(self, parent, config, name)
self.libraries_available = self.check_libraries_available()
if not self.libraries_available:

12
electrum/plugins/payserver/payserver.py

@ -60,7 +60,7 @@ class PayServerPlugin(BasePlugin):
# we use the first wallet loaded
if self.server is not None:
return
if self.config.get('offline'):
if self.config.NETWORK_OFFLINE:
return
self.server = PayServer(self.config, wallet)
asyncio.run_coroutine_threadsafe(daemon.taskgroup.spawn(self.server.run()), daemon.asyncio_loop)
@ -79,7 +79,7 @@ class PayServer(Logger, EventListener):
assert self.has_www_dir(), self.WWW_DIR
self.config = config
self.wallet = wallet
url = self.config.get('payserver_address', 'localhost:8080')
url = self.config.PAYSERVER_ADDRESS
self.addr = NetAddress.from_string(url)
self.pending = defaultdict(asyncio.Event)
self.register_callbacks()
@ -91,15 +91,15 @@ class PayServer(Logger, EventListener):
@property
def base_url(self):
payserver = self.config.get('payserver_address', 'localhost:8080')
payserver = self.config.PAYSERVER_ADDRESS
payserver = NetAddress.from_string(payserver)
use_ssl = bool(self.config.get('ssl_keyfile'))
use_ssl = bool(self.config.SSL_KEYFILE_PATH)
protocol = 'https' if use_ssl else 'http'
return '%s://%s:%d'%(protocol, payserver.host, payserver.port)
@property
def root(self):
return self.config.get('payserver_root', '/r')
return self.config.PAYSERVER_ROOT
@event_listener
async def on_event_request_status(self, wallet, key, status):
@ -118,7 +118,7 @@ class PayServer(Logger, EventListener):
# to minimise attack surface. note: "add_routes" call order matters (inner path goes first)
app.add_routes([web.static(f"{self.root}/vendor", os.path.join(self.WWW_DIR, 'vendor'), follow_symlinks=True)])
app.add_routes([web.static(self.root, self.WWW_DIR)])
if self.config.get('payserver_allow_create_invoice'):
if self.config.PAYSERVER_ALLOW_CREATE_INVOICE:
app.add_routes([web.post('/api/create_invoice', self.create_request)])
runner = web.AppRunner(app)
await runner.setup()

14
electrum/plugins/payserver/qt.py

@ -59,19 +59,19 @@ class Plugin(PayServerPlugin):
partial(self.settings_dialog, window))
def settings_dialog(self, window: WindowModalDialog):
if self.config.get('offline'):
if self.config.NETWORK_OFFLINE:
window.show_error(_("You are offline."))
return
d = WindowModalDialog(window, _("PayServer Settings"))
form = QtWidgets.QFormLayout(None)
addr = self.config.get('payserver_address', 'localhost:8080')
addr = self.config.PAYSERVER_ADDRESS
assert self.server
url = self.server.base_url + self.server.root + '/create_invoice.html'
self.help_button = QtWidgets.QPushButton('View sample invoice creation form')
self.help_button.clicked.connect(lambda: webopen(url))
address_e = QtWidgets.QLineEdit(addr)
keyfile_e = QtWidgets.QLineEdit(self.config.get('ssl_keyfile', ''))
certfile_e = QtWidgets.QLineEdit(self.config.get('ssl_certfile', ''))
keyfile_e = QtWidgets.QLineEdit(self.config.SSL_KEYFILE_PATH)
certfile_e = QtWidgets.QLineEdit(self.config.SSL_CERTFILE_PATH)
form.addRow(QtWidgets.QLabel("Network address:"), address_e)
form.addRow(QtWidgets.QLabel("SSL key file:"), keyfile_e)
form.addRow(QtWidgets.QLabel("SSL cert file:"), certfile_e)
@ -82,9 +82,9 @@ class Plugin(PayServerPlugin):
vbox.addSpacing(20)
vbox.addLayout(Buttons(OkButton(d)))
if d.exec_():
self.config.set_key('payserver_address', str(address_e.text()))
self.config.set_key('ssl_keyfile', str(keyfile_e.text()))
self.config.set_key('ssl_certfile', str(certfile_e.text()))
self.config.PAYSERVER_ADDRESS = str(address_e.text())
self.config.SSL_KEYFILE_PATH = str(keyfile_e.text())
self.config.SSL_CERTFILE_PATH = str(certfile_e.text())
# fixme: restart the server
window.show_message('Please restart Electrum to enable those changes')

4
electrum/plugins/trustedcoin/qt.py

@ -208,7 +208,9 @@ class Plugin(TrustedCoinPlugin):
grid.addWidget(QLabel(window.format_amount(v/k) + ' ' + window.base_unit() + "/tx"), i, 1)
b = QRadioButton()
b.setChecked(k == n_prepay)
b.clicked.connect(lambda b, k=k: self.config.set_key('trustedcoin_prepay', k, save=True))
def on_click(b, k):
self.config.PLUGIN_TRUSTEDCOIN_NUM_PREPAY = k
b.clicked.connect(partial(on_click, k=k))
grid.addWidget(b, i, 2)
i += 1

12
electrum/plugins/trustedcoin/trustedcoin.py

@ -296,11 +296,11 @@ class Wallet_2fa(Multisig_Wallet):
return min(self.price_per_tx.keys())
def num_prepay(self):
default = self.min_prepay()
n = self.config.get('trustedcoin_prepay', default)
if n not in self.price_per_tx:
n = default
return n
default_fallback = self.min_prepay()
num = self.config.PLUGIN_TRUSTEDCOIN_NUM_PREPAY
if num not in self.price_per_tx:
num = default_fallback
return num
def extra_fee(self):
if self.can_sign_without_server():
@ -559,7 +559,7 @@ class TrustedCoinPlugin(BasePlugin):
wizard.choice_dialog(title=title, message=message, choices=choices, run_next=wizard.run)
def choose_seed_type(self, wizard):
seed_type = '2fa' if self.config.get('nosegwit') else '2fa_segwit'
seed_type = '2fa' if self.config.WIZARD_DONT_CREATE_SEGWIT else '2fa_segwit'
self.create_seed(wizard, seed_type)
def create_seed(self, wizard, seed_type):

331
electrum/simple_config.py

@ -5,14 +5,16 @@ import os
import stat
import ssl
from decimal import Decimal
from typing import Union, Optional, Dict, Sequence, Tuple
from typing import Union, Optional, Dict, Sequence, Tuple, Any, Set
from numbers import Real
from functools import cached_property
from copy import deepcopy
from aiorpcx import NetAddress
from . import util
from . import constants
from . import invoices
from .util import base_units, base_unit_name_to_decimal_point, decimal_point_to_base_unit_name, UnknownBaseUnit, DECIMAL_POINT_DEFAULT
from .util import format_satoshis, format_fee_satoshis, os_chmod
from .util import user_dir, make_dir, NoDynamicFeeEstimates, quantize_feerate
@ -50,6 +52,72 @@ _logger = get_logger(__name__)
FINAL_CONFIG_VERSION = 3
class ConfigVar(property):
def __init__(self, key: str, *, default, type_=None):
self._key = key
self._default = default
self._type = type_
property.__init__(self, self._get_config_value, self._set_config_value)
def _get_config_value(self, config: 'SimpleConfig'):
value = config.get(self._key, default=self._default)
if self._type is not None and value != self._default:
assert value is not None, f"got None for key={self._key!r}"
try:
value = self._type(value)
except Exception as e:
raise ValueError(
f"ConfigVar.get type-check and auto-conversion failed. "
f"key={self._key!r}. type={self._type}. value={value!r}") from e
return value
def _set_config_value(self, config: 'SimpleConfig', value, *, save=True):
if self._type is not None and value is not None:
if not isinstance(value, self._type):
raise ValueError(
f"ConfigVar.set type-check failed. "
f"key={self._key!r}. type={self._type}. value={value!r}")
config.set_key(self._key, value, save=save)
def key(self) -> str:
return self._key
def get_default_value(self) -> Any:
return self._default
def __repr__(self):
return f"<ConfigVar key={self._key!r}>"
class ConfigVarWithConfig:
def __init__(self, *, config: 'SimpleConfig', config_var: 'ConfigVar'):
self._config = config
self._config_var = config_var
def get(self) -> Any:
return self._config_var._get_config_value(self._config)
def set(self, value: Any, *, save=True) -> None:
self._config_var._set_config_value(self._config, value, save=save)
def key(self) -> str:
return self._config_var.key()
def get_default_value(self) -> Any:
return self._config_var.get_default_value()
def is_modifiable(self) -> bool:
return self._config.is_modifiable(self._config_var)
def is_set(self) -> bool:
return self._config.is_set(self._config_var)
def __repr__(self):
return f"<ConfigVarWithConfig key={self.key()!r}>"
class SimpleConfig(Logger):
"""
The SimpleConfig class is responsible for handling operations involving
@ -98,7 +166,7 @@ class SimpleConfig(Logger):
# avoid new config getting upgraded
self.user_config = {'config_version': FINAL_CONFIG_VERSION}
self._not_modifiable_keys = set()
self._not_modifiable_keys = set() # type: Set[str]
# config "upgrade" - CLI options
self.rename_config_keys(
@ -111,14 +179,15 @@ class SimpleConfig(Logger):
self._check_dependent_keys()
# units and formatting
self.decimal_point = self.get('decimal_point', DECIMAL_POINT_DEFAULT)
# FIXME is this duplication (dp, nz, post_sat, thou_sep) due to performance reasons??
self.decimal_point = self.BTC_AMOUNTS_DECIMAL_POINT
try:
decimal_point_to_base_unit_name(self.decimal_point)
except UnknownBaseUnit:
self.decimal_point = DECIMAL_POINT_DEFAULT
self.num_zeros = int(self.get('num_zeros', 0))
self.amt_precision_post_satoshi = int(self.get('amt_precision_post_satoshi', 0))
self.amt_add_thousands_sep = bool(self.get('amt_add_thousands_sep', False))
self.num_zeros = self.BTC_AMOUNTS_FORCE_NZEROS_AFTER_DECIMAL_POINT
self.amt_precision_post_satoshi = self.BTC_AMOUNTS_PREC_POST_SAT
self.amt_add_thousands_sep = self.BTC_AMOUNTS_ADD_THOUSANDS_SEP
def electrum_path(self):
# Read electrum_path from command line
@ -158,7 +227,15 @@ class SimpleConfig(Logger):
updated = True
return updated
def set_key(self, key, value, *, save=True):
def set_key(self, key: Union[str, ConfigVar, ConfigVarWithConfig], value, *, save=True) -> None:
"""Set the value for an arbitrary string config key.
note: try to use explicit predefined ConfigVars instead of this method, whenever possible.
This method side-steps ConfigVars completely, and is mainly kept for situations
where the config key is dynamically constructed.
"""
if isinstance(key, (ConfigVar, ConfigVarWithConfig)):
key = key.key()
assert isinstance(key, str), key
if not self.is_modifiable(key):
self.logger.warning(f"not changing config key '{key}' set on the command line")
return
@ -170,7 +247,8 @@ class SimpleConfig(Logger):
return
self._set_key_in_user_config(key, value, save=save)
def _set_key_in_user_config(self, key, value, *, save=True):
def _set_key_in_user_config(self, key: str, value, *, save=True) -> None:
assert isinstance(key, str), key
with self.lock:
if value is not None:
self.user_config[key] = value
@ -179,18 +257,33 @@ class SimpleConfig(Logger):
if save:
self.save_user_config()
def get(self, key, default=None):
def get(self, key: str, default=None) -> Any:
"""Get the value for an arbitrary string config key.
note: try to use explicit predefined ConfigVars instead of this method, whenever possible.
This method side-steps ConfigVars completely, and is mainly kept for situations
where the config key is dynamically constructed.
"""
assert isinstance(key, str), key
with self.lock:
out = self.cmdline_options.get(key)
if out is None:
out = self.user_config.get(key, default)
return out
def is_set(self, key: Union[str, ConfigVar, ConfigVarWithConfig]) -> bool:
"""Returns whether the config key has any explicit value set/defined."""
if isinstance(key, (ConfigVar, ConfigVarWithConfig)):
key = key.key()
assert isinstance(key, str), key
return self.get(key, default=...) is not ...
def _check_dependent_keys(self) -> None:
if self.get('serverfingerprint'):
if not self.get('server'):
raise Exception("config key 'serverfingerprint' requires 'server' to also be set")
self.make_key_not_modifiable('server')
if self.NETWORK_SERVERFINGERPRINT:
if not self.NETWORK_SERVER:
raise Exception(
f"config key {self.__class__.NETWORK_SERVERFINGERPRINT.key()!r} requires "
f"{self.__class__.NETWORK_SERVER.key()!r} to also be set")
self.make_key_not_modifiable(self.__class__.NETWORK_SERVER)
def requires_upgrade(self):
return self.get_config_version() < FINAL_CONFIG_VERSION
@ -254,15 +347,20 @@ class SimpleConfig(Logger):
.format(config_version, FINAL_CONFIG_VERSION))
return config_version
def is_modifiable(self, key) -> bool:
def is_modifiable(self, key: Union[str, ConfigVar, ConfigVarWithConfig]) -> bool:
if isinstance(key, (ConfigVar, ConfigVarWithConfig)):
key = key.key()
return (key not in self.cmdline_options
and key not in self._not_modifiable_keys)
def make_key_not_modifiable(self, key) -> None:
def make_key_not_modifiable(self, key: Union[str, ConfigVar, ConfigVarWithConfig]) -> None:
if isinstance(key, (ConfigVar, ConfigVarWithConfig)):
key = key.key()
assert isinstance(key, str), key
self._not_modifiable_keys.add(key)
def save_user_config(self):
if self.get('forget_config'):
if self.CONFIG_FORGET_CHANGES:
return
if not self.path:
return
@ -277,13 +375,13 @@ class SimpleConfig(Logger):
if os.path.exists(self.path): # or maybe not?
raise
def get_backup_dir(self):
def get_backup_dir(self) -> Optional[str]:
# this is used to save wallet file backups (without active lightning channels)
# on Android, the export backup button uses android_backup_dir()
if 'ANDROID_DATA' in os.environ:
return None
else:
return self.get('backup_dir')
return self.WALLET_BACKUP_DIRECTORY
def get_wallet_path(self, *, use_gui_last_wallet=False):
"""Set the path of the wallet."""
@ -293,7 +391,7 @@ class SimpleConfig(Logger):
return os.path.join(self.get('cwd', ''), self.get('wallet_path'))
if use_gui_last_wallet:
path = self.get('gui_last_wallet')
path = self.GUI_LAST_WALLET
if path and os.path.exists(path):
return path
@ -314,22 +412,22 @@ class SimpleConfig(Logger):
return path
def remove_from_recently_open(self, filename):
recent = self.get('recently_open', [])
recent = self.RECENTLY_OPEN_WALLET_FILES or []
if filename in recent:
recent.remove(filename)
self.set_key('recently_open', recent)
self.RECENTLY_OPEN_WALLET_FILES = recent
def set_session_timeout(self, seconds):
self.logger.info(f"session timeout -> {seconds} seconds")
self.set_key('session_timeout', seconds)
self.HWD_SESSION_TIMEOUT = seconds
def get_session_timeout(self):
return self.get('session_timeout', 300)
return self.HWD_SESSION_TIMEOUT
def save_last_wallet(self, wallet):
if self.get('wallet_path') is None:
path = wallet.storage.path
self.set_key('gui_last_wallet', path)
self.GUI_LAST_WALLET = path
def impose_hard_limits_on_fee(func):
def get_fee_within_limits(self, *args, **kwargs):
@ -511,13 +609,13 @@ class SimpleConfig(Logger):
tooltip = ''
return text, tooltip
def get_depth_level(self):
def get_depth_level(self) -> int:
maxp = len(FEE_DEPTH_TARGETS) - 1
return min(maxp, self.get('depth_level', 2))
return min(maxp, self.FEE_EST_DYNAMIC_MEMPOOL_SLIDERPOS)
def get_fee_level(self):
def get_fee_level(self) -> int:
maxp = len(FEE_ETA_TARGETS) # not (-1) to have "next block"
return min(maxp, self.get('fee_level', 2))
return min(maxp, self.FEE_EST_DYNAMIC_ETA_SLIDERPOS)
def get_fee_slider(self, dyn, mempool) -> Tuple[int, int, Optional[int]]:
if dyn:
@ -556,11 +654,11 @@ class SimpleConfig(Logger):
else:
return self.has_fee_etas()
def is_dynfee(self):
return bool(self.get('dynamic_fees', True))
def is_dynfee(self) -> bool:
return self.FEE_EST_DYNAMIC
def use_mempool_fees(self):
return bool(self.get('mempool_fees', False))
def use_mempool_fees(self) -> bool:
return self.FEE_EST_USE_MEMPOOL
def _feerate_from_fractional_slider_position(self, fee_level: float, dyn: bool,
mempool: bool) -> Union[int, None]:
@ -599,7 +697,7 @@ class SimpleConfig(Logger):
else:
fee_rate = self.eta_to_fee(self.get_fee_level())
else:
fee_rate = self.get('fee_per_kb', FEERATE_FALLBACK_STATIC_FEE)
fee_rate = self.FEE_EST_STATIC_FEERATE_FALLBACK
if fee_rate is not None:
fee_rate = int(fee_rate)
return fee_rate
@ -648,14 +746,14 @@ class SimpleConfig(Logger):
self.last_time_fee_estimates_requested = time.time()
def get_video_device(self):
device = self.get("video_device", "default")
device = self.VIDEO_DEVICE_PATH
if device == 'default':
device = ''
return device
def get_ssl_context(self):
ssl_keyfile = self.get('ssl_keyfile')
ssl_certfile = self.get('ssl_certfile')
ssl_keyfile = self.SSL_KEYFILE_PATH
ssl_certfile = self.SSL_CERTFILE_PATH
if ssl_keyfile and ssl_certfile:
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_context.load_cert_chain(ssl_certfile, ssl_keyfile)
@ -663,13 +761,16 @@ class SimpleConfig(Logger):
def get_ssl_domain(self):
from .paymentrequest import check_ssl_config
if self.get('ssl_keyfile') and self.get('ssl_certfile'):
if self.SSL_KEYFILE_PATH and self.SSL_CERTFILE_PATH:
SSL_identity = check_ssl_config(self)
else:
SSL_identity = None
return SSL_identity
def get_netaddress(self, key: str) -> Optional[NetAddress]:
def get_netaddress(self, key: Union[str, ConfigVar, ConfigVarWithConfig]) -> Optional[NetAddress]:
if isinstance(key, (ConfigVar, ConfigVarWithConfig)):
key = key.key()
assert isinstance(key, str), key
text = self.get(key)
if text:
try:
@ -709,13 +810,163 @@ class SimpleConfig(Logger):
def set_base_unit(self, unit):
assert unit in base_units.keys()
self.decimal_point = base_unit_name_to_decimal_point(unit)
self.set_key('decimal_point', self.decimal_point, save=True)
self.BTC_AMOUNTS_DECIMAL_POINT = self.decimal_point
def get_decimal_point(self):
return self.decimal_point
@cached_property
def cv(config):
"""Allows getting a reference to a config variable without dereferencing it.
def read_user_config(path):
Compare:
>>> config.NETWORK_SERVER
'testnet.hsmiths.com:53012:s'
>>> config.cv.NETWORK_SERVER
<ConfigVarWithConfig key='server'>
"""
class CVLookupHelper:
def __getattribute__(self, name: str) -> ConfigVarWithConfig:
config_var = config.__class__.__getattribute__(type(config), name)
if not isinstance(config_var, ConfigVar):
raise AttributeError()
return ConfigVarWithConfig(config=config, config_var=config_var)
def __setattr__(self, name, value):
raise Exception(
f"Cannot assign value to config.cv.{name} directly. "
f"Either use config.cv.{name}.set() or assign to config.{name} instead.")
return CVLookupHelper()
# config variables ----->
NETWORK_AUTO_CONNECT = ConfigVar('auto_connect', default=True, type_=bool)
NETWORK_ONESERVER = ConfigVar('oneserver', default=False, type_=bool)
NETWORK_PROXY = ConfigVar('proxy', default=None)
NETWORK_SERVER = ConfigVar('server', default=None, type_=str)
NETWORK_NOONION = ConfigVar('noonion', default=False, type_=bool)
NETWORK_OFFLINE = ConfigVar('offline', default=False, type_=bool)
NETWORK_SKIPMERKLECHECK = ConfigVar('skipmerklecheck', default=False, type_=bool)
NETWORK_SERVERFINGERPRINT = ConfigVar('serverfingerprint', default=None, type_=str)
NETWORK_MAX_INCOMING_MSG_SIZE = ConfigVar('network_max_incoming_msg_size', default=1_000_000, type_=int) # in bytes
NETWORK_TIMEOUT = ConfigVar('network_timeout', default=None, type_=int)
WALLET_BATCH_RBF = ConfigVar('batch_rbf', default=False, type_=bool)
WALLET_SPEND_CONFIRMED_ONLY = ConfigVar('confirmed_only', default=False, type_=bool)
WALLET_COIN_CHOOSER_POLICY = ConfigVar('coin_chooser', default='Privacy', type_=str)
WALLET_COIN_CHOOSER_OUTPUT_ROUNDING = ConfigVar('coin_chooser_output_rounding', default=True, type_=bool)
WALLET_UNCONF_UTXO_FREEZE_THRESHOLD_SAT = ConfigVar('unconf_utxo_freeze_threshold', default=5_000, type_=int)
WALLET_BIP21_LIGHTNING = ConfigVar('bip21_lightning', default=False, type_=bool)
WALLET_BOLT11_FALLBACK = ConfigVar('bolt11_fallback', default=True, type_=bool)
WALLET_PAYREQ_EXPIRY_SECONDS = ConfigVar('request_expiry', default=invoices.PR_DEFAULT_EXPIRATION_WHEN_CREATING, type_=int)
WALLET_USE_SINGLE_PASSWORD = ConfigVar('single_password', default=False, type_=bool)
# note: 'use_change' and 'multiple_change' are per-wallet settings
FX_USE_EXCHANGE_RATE = ConfigVar('use_exchange_rate', default=False, type_=bool)
FX_CURRENCY = ConfigVar('currency', default='EUR', type_=str)
FX_EXCHANGE = ConfigVar('use_exchange', default='CoinGecko', type_=str) # default exchange should ideally provide historical rates
FX_HISTORY_RATES = ConfigVar('history_rates', default=False, type_=bool)
FX_HISTORY_RATES_CAPITAL_GAINS = ConfigVar('history_rates_capital_gains', default=False, type_=bool)
FX_SHOW_FIAT_BALANCE_FOR_ADDRESSES = ConfigVar('fiat_address', default=False, type_=bool)
LIGHTNING_LISTEN = ConfigVar('lightning_listen', default=None, type_=str)
LIGHTNING_PEERS = ConfigVar('lightning_peers', default=None)
LIGHTNING_USE_GOSSIP = ConfigVar('use_gossip', default=False, type_=bool)
LIGHTNING_USE_RECOVERABLE_CHANNELS = ConfigVar('use_recoverable_channels', default=True, type_=bool)
LIGHTNING_ALLOW_INSTANT_SWAPS = ConfigVar('allow_instant_swaps', default=False, type_=bool)
LIGHTNING_TO_SELF_DELAY_CSV = ConfigVar('lightning_to_self_delay', default=7 * 144, type_=int)
EXPERIMENTAL_LN_FORWARD_PAYMENTS = ConfigVar('lightning_forward_payments', default=False, type_=bool)
EXPERIMENTAL_LN_FORWARD_TRAMPOLINE_PAYMENTS = ConfigVar('lightning_forward_trampoline_payments', default=False, type_=bool)
TEST_FAIL_HTLCS_WITH_TEMP_NODE_FAILURE = ConfigVar('test_fail_htlcs_with_temp_node_failure', default=False, type_=bool)
TEST_FAIL_HTLCS_AS_MALFORMED = ConfigVar('test_fail_malformed_htlc', default=False, type_=bool)
TEST_SHUTDOWN_FEE = ConfigVar('test_shutdown_fee', default=None, type_=int)
TEST_SHUTDOWN_FEE_RANGE = ConfigVar('test_shutdown_fee_range', default=None)
TEST_SHUTDOWN_LEGACY = ConfigVar('test_shutdown_legacy', default=False, type_=bool)
FEE_EST_DYNAMIC = ConfigVar('dynamic_fees', default=True, type_=bool)
FEE_EST_USE_MEMPOOL = ConfigVar('mempool_fees', default=False, type_=bool)
FEE_EST_STATIC_FEERATE_FALLBACK = ConfigVar('fee_per_kb', default=FEERATE_FALLBACK_STATIC_FEE, type_=int)
FEE_EST_DYNAMIC_ETA_SLIDERPOS = ConfigVar('fee_level', default=2, type_=int)
FEE_EST_DYNAMIC_MEMPOOL_SLIDERPOS = ConfigVar('depth_level', default=2, type_=int)
RPC_USERNAME = ConfigVar('rpcuser', default=None, type_=str)
RPC_PASSWORD = ConfigVar('rpcpassword', default=None, type_=str)
RPC_HOST = ConfigVar('rpchost', default='127.0.0.1', type_=str)
RPC_PORT = ConfigVar('rpcport', default=0, type_=int)
RPC_SOCKET_TYPE = ConfigVar('rpcsock', default='auto', type_=str)
RPC_SOCKET_FILEPATH = ConfigVar('rpcsockpath', default=None, type_=str)
GUI_NAME = ConfigVar('gui', default='qt', type_=str)
GUI_LAST_WALLET = ConfigVar('gui_last_wallet', default=None, type_=str)
GUI_QT_COLOR_THEME = ConfigVar('qt_gui_color_theme', default='default', type_=str)
GUI_QT_DARK_TRAY_ICON = ConfigVar('dark_icon', default=False, type_=bool)
GUI_QT_WINDOW_IS_MAXIMIZED = ConfigVar('is_maximized', default=False, type_=bool)
GUI_QT_HIDE_ON_STARTUP = ConfigVar('hide_gui', default=False, type_=bool)
GUI_QT_HISTORY_TAB_SHOW_TOOLBAR = ConfigVar('show_toolbar_history', default=False, type_=bool)
GUI_QT_ADDRESSES_TAB_SHOW_TOOLBAR = ConfigVar('show_toolbar_addresses', default=False, type_=bool)
GUI_QT_TX_DIALOG_FETCH_TXIN_DATA = ConfigVar('tx_dialog_fetch_txin_data', default=False, type_=bool)
GUI_QT_RECEIVE_TABS_INDEX = ConfigVar('receive_tabs_index', default=0, type_=int)
GUI_QT_RECEIVE_TAB_QR_VISIBLE = ConfigVar('receive_qr_visible', default=False, type_=bool)
GUI_QT_TX_EDITOR_SHOW_IO = ConfigVar('show_tx_io', default=False, type_=bool)
GUI_QT_TX_EDITOR_SHOW_FEE_DETAILS = ConfigVar('show_tx_fee_details', default=False, type_=bool)
GUI_QT_TX_EDITOR_SHOW_LOCKTIME = ConfigVar('show_tx_locktime', default=False, type_=bool)
GUI_QT_SHOW_TAB_ADDRESSES = ConfigVar('show_addresses_tab', default=False, type_=bool)
GUI_QT_SHOW_TAB_CHANNELS = ConfigVar('show_channels_tab', default=False, type_=bool)
GUI_QT_SHOW_TAB_UTXO = ConfigVar('show_utxo_tab', default=False, type_=bool)
GUI_QT_SHOW_TAB_CONTACTS = ConfigVar('show_contacts_tab', default=False, type_=bool)
GUI_QT_SHOW_TAB_CONSOLE = ConfigVar('show_console_tab', default=False, type_=bool)
GUI_QML_PREFERRED_REQUEST_TYPE = ConfigVar('preferred_request_type', default='bolt11', type_=str)
GUI_QML_USER_KNOWS_PRESS_AND_HOLD = ConfigVar('user_knows_press_and_hold', default=False, type_=bool)
BTC_AMOUNTS_DECIMAL_POINT = ConfigVar('decimal_point', default=DECIMAL_POINT_DEFAULT, type_=int)
BTC_AMOUNTS_FORCE_NZEROS_AFTER_DECIMAL_POINT = ConfigVar('num_zeros', default=0, type_=int)
BTC_AMOUNTS_PREC_POST_SAT = ConfigVar('amt_precision_post_satoshi', default=0, type_=int)
BTC_AMOUNTS_ADD_THOUSANDS_SEP = ConfigVar('amt_add_thousands_sep', default=False, type_=bool)
BLOCK_EXPLORER = ConfigVar('block_explorer', default='Blockstream.info', type_=str)
BLOCK_EXPLORER_CUSTOM = ConfigVar('block_explorer_custom', default=None)
VIDEO_DEVICE_PATH = ConfigVar('video_device', default='default', type_=str)
OPENALIAS_ID = ConfigVar('alias', default="", type_=str)
HWD_SESSION_TIMEOUT = ConfigVar('session_timeout', default=300, type_=int)
CLI_TIMEOUT = ConfigVar('timeout', default=60, type_=float)
AUTOMATIC_CENTRALIZED_UPDATE_CHECKS = ConfigVar('check_updates', default=False, type_=bool)
WRITE_LOGS_TO_DISK = ConfigVar('log_to_file', default=False, type_=bool)
GUI_ENABLE_DEBUG_LOGS = ConfigVar('gui_enable_debug_logs', default=False, type_=bool)
LOCALIZATION_LANGUAGE = ConfigVar('language', default="", type_=str)
BLOCKCHAIN_PREFERRED_BLOCK = ConfigVar('blockchain_preferred_block', default=None)
SHOW_CRASH_REPORTER = ConfigVar('show_crash_reporter', default=True, type_=bool)
DONT_SHOW_TESTNET_WARNING = ConfigVar('dont_show_testnet_warning', default=False, type_=bool)
RECENTLY_OPEN_WALLET_FILES = ConfigVar('recently_open', default=None)
IO_DIRECTORY = ConfigVar('io_dir', default=os.path.expanduser('~'), type_=str)
WALLET_BACKUP_DIRECTORY = ConfigVar('backup_dir', default=None, type_=str)
CONFIG_PIN_CODE = ConfigVar('pin_code', default=None, type_=str)
QR_READER_FLIP_X = ConfigVar('qrreader_flip_x', default=True, type_=bool)
WIZARD_DONT_CREATE_SEGWIT = ConfigVar('nosegwit', default=False, type_=bool)
CONFIG_FORGET_CHANGES = ConfigVar('forget_config', default=False, type_=bool)
SSL_CERTFILE_PATH = ConfigVar('ssl_certfile', default='', type_=str)
SSL_KEYFILE_PATH = ConfigVar('ssl_keyfile', default='', type_=str)
# connect to remote WT
WATCHTOWER_CLIENT_ENABLED = ConfigVar('use_watchtower', default=False, type_=bool)
WATCHTOWER_CLIENT_URL = ConfigVar('watchtower_url', default=None, type_=str)
# run WT locally
WATCHTOWER_SERVER_ENABLED = ConfigVar('run_watchtower', default=False, type_=bool)
WATCHTOWER_SERVER_ADDRESS = ConfigVar('watchtower_address', default=None, type_=str)
WATCHTOWER_SERVER_USER = ConfigVar('watchtower_user', default=None, type_=str)
WATCHTOWER_SERVER_PASSWORD = ConfigVar('watchtower_password', default=None, type_=str)
PAYSERVER_ADDRESS = ConfigVar('payserver_address', default='localhost:8080', type_=str)
PAYSERVER_ROOT = ConfigVar('payserver_root', default='/r', type_=str)
PAYSERVER_ALLOW_CREATE_INVOICE = ConfigVar('payserver_allow_create_invoice', default=False, type_=bool)
PLUGIN_TRUSTEDCOIN_NUM_PREPAY = ConfigVar('trustedcoin_prepay', default=20, type_=int)
def read_user_config(path: Optional[str]) -> Dict[str, Any]:
"""Parse and store the user config settings in electrum.conf into user_config[]."""
if not path:
return {}

2
electrum/submarine_swaps.py

@ -203,7 +203,7 @@ class SwapManager(Logger):
self.lnwatcher.remove_callback(swap.lockup_address)
swap.is_redeemed = True
elif spent_height == TX_HEIGHT_LOCAL:
if txin.block_height > 0 or self.wallet.config.get('allow_instant_swaps', False):
if txin.block_height > 0 or self.wallet.config.LIGHTNING_ALLOW_INSTANT_SWAPS:
tx = self.lnwatcher.adb.get_transaction(txin.spent_txid)
self.logger.info(f'broadcasting tx {txin.spent_txid}')
await self.network.broadcast_transaction(tx)

4
electrum/tests/test_daemon.py

@ -15,8 +15,8 @@ class TestUnifiedPassword(ElectrumTestCase):
def setUp(self):
super().setUp()
self.config = SimpleConfig({'electrum_path': self.electrum_path})
self.config.set_key("single_password", True)
self.config.set_key("offline", True)
self.config.WALLET_USE_SINGLE_PASSWORD = True
self.config.NETWORK_OFFLINE = True
self.wallet_dir = os.path.dirname(self.config.get_wallet_path())
assert "wallets" == os.path.basename(self.wallet_dir)

42
electrum/tests/test_lnpeer.py

@ -366,8 +366,8 @@ GRAPH_DEFINITIONS = {
'dave': high_fee_channel.copy(),
},
'config': {
'lightning_forward_payments': True,
'lightning_forward_trampoline_payments': True,
SimpleConfig.EXPERIMENTAL_LN_FORWARD_PAYMENTS: True,
SimpleConfig.EXPERIMENTAL_LN_FORWARD_TRAMPOLINE_PAYMENTS: True,
},
},
'carol': {
@ -375,8 +375,8 @@ GRAPH_DEFINITIONS = {
'dave': low_fee_channel.copy(),
},
'config': {
'lightning_forward_payments': True,
'lightning_forward_trampoline_payments': True,
SimpleConfig.EXPERIMENTAL_LN_FORWARD_PAYMENTS: True,
SimpleConfig.EXPERIMENTAL_LN_FORWARD_TRAMPOLINE_PAYMENTS: True,
},
},
'dave': {
@ -932,8 +932,8 @@ class TestPeer(ElectrumTestCase):
@needs_test_with_all_chacha20_implementations
async def test_payment_multihop_temp_node_failure(self):
graph = self.prepare_chans_and_peers_in_graph(GRAPH_DEFINITIONS['square_graph'])
graph.workers['bob'].network.config.set_key('test_fail_htlcs_with_temp_node_failure', True)
graph.workers['carol'].network.config.set_key('test_fail_htlcs_with_temp_node_failure', True)
graph.workers['bob'].network.config.TEST_FAIL_HTLCS_WITH_TEMP_NODE_FAILURE = True
graph.workers['carol'].network.config.TEST_FAIL_HTLCS_WITH_TEMP_NODE_FAILURE = True
peers = graph.peers.values()
async def pay(lnaddr, pay_req):
self.assertEqual(PR_UNPAID, graph.workers['dave'].get_payment_status(lnaddr.paymenthash))
@ -959,7 +959,7 @@ class TestPeer(ElectrumTestCase):
# Alice will pay Dave. Alice first tries A->C->D route, due to lower fees, but Carol
# will fail the htlc and get blacklisted. Alice will then try A->B->D and succeed.
graph = self.prepare_chans_and_peers_in_graph(GRAPH_DEFINITIONS['square_graph'])
graph.workers['carol'].network.config.set_key('test_fail_htlcs_with_temp_node_failure', True)
graph.workers['carol'].network.config.TEST_FAIL_HTLCS_WITH_TEMP_NODE_FAILURE = True
peers = graph.peers.values()
async def pay(lnaddr, pay_req):
self.assertEqual(500000000000, graph.channels[('alice', 'bob')].balance(LOCAL))
@ -1298,16 +1298,16 @@ class TestPeer(ElectrumTestCase):
async def _test_shutdown(self, alice_fee, bob_fee, alice_fee_range=None, bob_fee_range=None):
alice_channel, bob_channel = create_test_channels()
p1, p2, w1, w2, _q1, _q2 = self.prepare_peers(alice_channel, bob_channel)
w1.network.config.set_key('test_shutdown_fee', alice_fee)
w2.network.config.set_key('test_shutdown_fee', bob_fee)
w1.network.config.TEST_SHUTDOWN_FEE = alice_fee
w2.network.config.TEST_SHUTDOWN_FEE = bob_fee
if alice_fee_range is not None:
w1.network.config.set_key('test_shutdown_fee_range', alice_fee_range)
w1.network.config.TEST_SHUTDOWN_FEE_RANGE = alice_fee_range
else:
w1.network.config.set_key('test_shutdown_legacy', True)
w1.network.config.TEST_SHUTDOWN_LEGACY = True
if bob_fee_range is not None:
w2.network.config.set_key('test_shutdown_fee_range', bob_fee_range)
w2.network.config.TEST_SHUTDOWN_FEE_RANGE = bob_fee_range
else:
w2.network.config.set_key('test_shutdown_legacy', True)
w2.network.config.TEST_SHUTDOWN_LEGACY = True
w2.enable_htlc_settle = False
lnaddr, pay_req = self.prepare_invoice(w2)
async def pay():
@ -1377,10 +1377,10 @@ class TestPeer(ElectrumTestCase):
bob_channel.config[HTLCOwner.LOCAL].upfront_shutdown_script = b''
p1, p2, w1, w2, q1, q2 = self.prepare_peers(alice_channel, bob_channel)
w1.network.config.set_key('dynamic_fees', False)
w2.network.config.set_key('dynamic_fees', False)
w1.network.config.set_key('fee_per_kb', 5000)
w2.network.config.set_key('fee_per_kb', 1000)
w1.network.config.FEE_EST_DYNAMIC = False
w2.network.config.FEE_EST_DYNAMIC = False
w1.network.config.FEE_EST_STATIC_FEERATE_FALLBACK = 5000
w2.network.config.FEE_EST_STATIC_FEERATE_FALLBACK = 1000
async def test():
async def close():
@ -1407,10 +1407,10 @@ class TestPeer(ElectrumTestCase):
bob_channel.config[HTLCOwner.LOCAL].upfront_shutdown_script = bob_uss
p1, p2, w1, w2, q1, q2 = self.prepare_peers(alice_channel, bob_channel)
w1.network.config.set_key('dynamic_fees', False)
w2.network.config.set_key('dynamic_fees', False)
w1.network.config.set_key('fee_per_kb', 5000)
w2.network.config.set_key('fee_per_kb', 1000)
w1.network.config.FEE_EST_DYNAMIC = False
w2.network.config.FEE_EST_DYNAMIC = False
w1.network.config.FEE_EST_STATIC_FEERATE_FALLBACK = 5000
w2.network.config.FEE_EST_STATIC_FEERATE_FALLBACK = 1000
async def test():
async def close():

73
electrum/tests/test_simple_config.py

@ -10,6 +10,10 @@ from electrum.simple_config import (SimpleConfig, read_user_config)
from . import ElectrumTestCase
MAX_MSG_SIZE_DEFAULT = SimpleConfig.NETWORK_MAX_INCOMING_MSG_SIZE.get_default_value()
assert isinstance(MAX_MSG_SIZE_DEFAULT, int), MAX_MSG_SIZE_DEFAULT
class Test_SimpleConfig(ElectrumTestCase):
def setUp(self):
@ -109,6 +113,75 @@ class Test_SimpleConfig(ElectrumTestCase):
result.pop('config_version', None)
self.assertEqual({"something": "a"}, result)
def test_configvars_set_and_get(self):
config = SimpleConfig(self.options)
self.assertEqual("server", config.cv.NETWORK_SERVER.key())
def _set_via_assignment():
config.NETWORK_SERVER = "example.com:443:s"
for f in (
lambda: config.set_key("server", "example.com:443:s"),
_set_via_assignment,
lambda: config.cv.NETWORK_SERVER.set("example.com:443:s"),
):
self.assertTrue(config.get("server") is None)
self.assertTrue(config.NETWORK_SERVER is None)
self.assertTrue(config.cv.NETWORK_SERVER.get() is None)
f()
self.assertEqual("example.com:443:s", config.get("server"))
self.assertEqual("example.com:443:s", config.NETWORK_SERVER)
self.assertEqual("example.com:443:s", config.cv.NETWORK_SERVER.get())
# revert:
config.NETWORK_SERVER = None
def test_configvars_get_default_value(self):
config = SimpleConfig(self.options)
self.assertEqual(MAX_MSG_SIZE_DEFAULT, config.cv.NETWORK_MAX_INCOMING_MSG_SIZE.get_default_value())
self.assertEqual(MAX_MSG_SIZE_DEFAULT, config.NETWORK_MAX_INCOMING_MSG_SIZE)
config.NETWORK_MAX_INCOMING_MSG_SIZE = 5_555_555
self.assertEqual(5_555_555, config.NETWORK_MAX_INCOMING_MSG_SIZE)
self.assertEqual(MAX_MSG_SIZE_DEFAULT, config.cv.NETWORK_MAX_INCOMING_MSG_SIZE.get_default_value())
config.NETWORK_MAX_INCOMING_MSG_SIZE = None
self.assertEqual(MAX_MSG_SIZE_DEFAULT, config.NETWORK_MAX_INCOMING_MSG_SIZE)
def test_configvars_is_set(self):
config = SimpleConfig(self.options)
self.assertEqual(MAX_MSG_SIZE_DEFAULT, config.NETWORK_MAX_INCOMING_MSG_SIZE)
self.assertFalse(config.cv.NETWORK_MAX_INCOMING_MSG_SIZE.is_set())
config.NETWORK_MAX_INCOMING_MSG_SIZE = 5_555_555
self.assertTrue(config.cv.NETWORK_MAX_INCOMING_MSG_SIZE.is_set())
config.NETWORK_MAX_INCOMING_MSG_SIZE = None
self.assertFalse(config.cv.NETWORK_MAX_INCOMING_MSG_SIZE.is_set())
self.assertEqual(MAX_MSG_SIZE_DEFAULT, config.NETWORK_MAX_INCOMING_MSG_SIZE)
config.NETWORK_MAX_INCOMING_MSG_SIZE = MAX_MSG_SIZE_DEFAULT
self.assertTrue(config.cv.NETWORK_MAX_INCOMING_MSG_SIZE.is_set())
self.assertEqual(MAX_MSG_SIZE_DEFAULT, config.NETWORK_MAX_INCOMING_MSG_SIZE)
def test_configvars_is_modifiable(self):
config = SimpleConfig({**self.options, "server": "example.com:443:s"})
self.assertFalse(config.is_modifiable("server"))
self.assertFalse(config.cv.NETWORK_SERVER.is_modifiable())
config.NETWORK_SERVER = "other-example.com:80:t"
self.assertEqual("example.com:443:s", config.NETWORK_SERVER)
self.assertEqual(MAX_MSG_SIZE_DEFAULT, config.NETWORK_MAX_INCOMING_MSG_SIZE)
self.assertTrue(config.cv.NETWORK_MAX_INCOMING_MSG_SIZE.is_modifiable())
config.NETWORK_MAX_INCOMING_MSG_SIZE = 5_555_555
self.assertEqual(5_555_555, config.NETWORK_MAX_INCOMING_MSG_SIZE)
config.make_key_not_modifiable(config.cv.NETWORK_MAX_INCOMING_MSG_SIZE)
self.assertFalse(config.cv.NETWORK_MAX_INCOMING_MSG_SIZE.is_modifiable())
config.NETWORK_MAX_INCOMING_MSG_SIZE = 2_222_222
self.assertEqual(5_555_555, config.NETWORK_MAX_INCOMING_MSG_SIZE)
def test_depth_target_to_fee(self):
config = SimpleConfig(self.options)
config.mempool_fees = [[49, 100110], [10, 121301], [6, 153731], [5, 125872], [1, 36488810]]

4
electrum/tests/test_sswaps.py

@ -12,8 +12,8 @@ class TestSwapTxs(ElectrumTestCase):
def setUp(self):
super().setUp()
self.config = SimpleConfig({'electrum_path': self.electrum_path})
self.config.set_key('dynamic_fees', False)
self.config.set_key('fee_per_kb', 1000)
self.config.FEE_EST_DYNAMIC = False
self.config.FEE_EST_STATIC_FEERATE_FALLBACK = 1000
def test_claim_tx_for_successful_reverse_swap(self):
swap_data = SwapData(

10
electrum/tests/test_wallet_vertical.py

@ -1040,7 +1040,7 @@ class TestWalletSending(ElectrumTestCase):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.config = SimpleConfig({'electrum_path': self.name})
self.config.set_key('coin_chooser_output_rounding', False)
self.config.WALLET_COIN_CHOOSER_OUTPUT_ROUNDING = False
def __enter__(self):
return self.config
@ -1744,7 +1744,7 @@ class TestWalletSending(ElectrumTestCase):
async def _rbf_batching(self, *, simulate_moving_txs, config):
wallet = self.create_standard_wallet_from_seed('frost repair depend effort salon ring foam oak cancel receive save usage',
config=config)
wallet.config.set_key('batch_rbf', True)
wallet.config.WALLET_BATCH_RBF = True
# bootstrap wallet (incoming funding_tx1)
funding_tx1 = Transaction('01000000000102acd6459dec7c3c51048eb112630da756f5d4cb4752b8d39aa325407ae0885cba020000001716001455c7f5e0631d8e6f5f05dddb9f676cec48845532fdffffffd146691ef6a207b682b13da5f2388b1f0d2a2022c8cfb8dc27b65434ec9ec8f701000000171600147b3be8a7ceaf15f57d7df2a3d216bc3c259e3225fdffffff02a9875b000000000017a914ea5a99f83e71d1c1dfc5d0370e9755567fe4a141878096980000000000160014d4ca56fcbad98fb4dcafdc573a75d6a6fffb09b702483045022100dde1ba0c9a2862a65791b8d91295a6603207fb79635935a67890506c214dd96d022046c6616642ef5971103c1db07ac014e63fa3b0e15c5729eacdd3e77fcb7d2086012103a72410f185401bb5b10aaa30989c272b554dc6d53bda6da85a76f662723421af024730440220033d0be8f74e782fbcec2b396647c7715d2356076b442423f23552b617062312022063c95cafdc6d52ccf55c8ee0f9ceb0f57afb41ea9076eb74fe633f59c50c6377012103b96a4954d834fbcfb2bbf8cf7de7dc2b28bc3d661c1557d1fd1db1bfc123a94abb391400')
@ -1879,7 +1879,7 @@ class TestWalletSending(ElectrumTestCase):
coins = wallet.get_spendable_coins(domain=None)
self.assertEqual(2, len(coins))
wallet.config.set_key('batch_rbf', batch_rbf)
wallet.config.WALLET_BATCH_RBF = batch_rbf
tx = wallet.make_unsigned_transaction(coins=coins, outputs=outputs, fee=1000)
tx.set_rbf(True)
tx.locktime = 2423302
@ -2211,7 +2211,7 @@ class TestWalletSending(ElectrumTestCase):
async def test_dscancel(self, mock_save_db):
self.maxDiff = None
config = SimpleConfig({'electrum_path': self.electrum_path})
config.set_key('coin_chooser_output_rounding', False)
config.WALLET_COIN_CHOOSER_OUTPUT_ROUNDING = False
for simulate_moving_txs in (False, True):
with self.subTest(msg="_dscancel_when_all_outputs_are_ismine", simulate_moving_txs=simulate_moving_txs):
@ -3691,8 +3691,8 @@ class TestWalletHistory_EvilGapLimit(ElectrumTestCase):
super().setUp()
self.config = SimpleConfig({
'electrum_path': self.electrum_path,
'skipmerklecheck': True, # needed for Synchronizer to generate new addresses without SPV
})
self.config.NETWORK_SKIPMERKLECHECK = True # needed for Synchronizer to generate new addresses without SPV
def create_wallet(self):
ks = keystore.from_xpub('vpub5Vhmk4dEJKanDTTw6immKXa3thw45u3gbd1rPYjREB6viP13sVTWcH6kvbR2YeLtGjradr6SFLVt9PxWDBSrvw1Dc1nmd3oko3m24CQbfaJ')

11
electrum/util.py

@ -974,25 +974,24 @@ def block_explorer(config: 'SimpleConfig') -> Optional[str]:
"""Returns name of selected block explorer,
or None if a custom one (not among hardcoded ones) is configured.
"""
if config.get('block_explorer_custom') is not None:
if config.BLOCK_EXPLORER_CUSTOM is not None:
return None
default_ = 'Blockstream.info'
be_key = config.get('block_explorer', default_)
be_key = config.BLOCK_EXPLORER
be_tuple = block_explorer_info().get(be_key)
if be_tuple is None:
be_key = default_
be_key = config.cv.BLOCK_EXPLORER.get_default_value()
assert isinstance(be_key, str), f"{be_key!r} should be str"
return be_key
def block_explorer_tuple(config: 'SimpleConfig') -> Optional[Tuple[str, dict]]:
custom_be = config.get('block_explorer_custom')
custom_be = config.BLOCK_EXPLORER_CUSTOM
if custom_be:
if isinstance(custom_be, str):
return custom_be, _block_explorer_default_api_loc
if isinstance(custom_be, (tuple, list)) and len(custom_be) == 2:
return tuple(custom_be)
_logger.warning(f"not using 'block_explorer_custom' from config. "
_logger.warning(f"not using {config.cv.BLOCK_EXPLORER_CUSTOM.key()!r} from config. "
f"expected a str or a pair but got {custom_be!r}")
return None
else:

2
electrum/verifier.py

@ -121,7 +121,7 @@ class SPV(NetworkJobOnDefaultServer):
try:
verify_tx_is_in_block(tx_hash, merkle_branch, pos, header, tx_height)
except MerkleVerificationFailure as e:
if self.network.config.get("skipmerklecheck"):
if self.network.config.NETWORK_SKIPMERKLECHECK:
self.logger.info(f"skipping merkle proof check {tx_hash}")
else:
self.logger.info(repr(e))

8
electrum/wallet.py

@ -1731,7 +1731,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
# Let the coin chooser select the coins to spend
coin_chooser = coinchooser.get_coin_chooser(self.config)
# If there is an unconfirmed RBF tx, merge with it
base_tx = self.get_unconfirmed_base_tx_for_batching(outputs, coins) if self.config.get('batch_rbf', False) else None
base_tx = self.get_unconfirmed_base_tx_for_batching(outputs, coins) if self.config.WALLET_BATCH_RBF else None
if base_tx:
# make sure we don't try to spend change from the tx-to-be-replaced:
coins = [c for c in coins if c.prevout.txid.hex() != base_tx.txid()]
@ -1847,7 +1847,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
# exempt large value UTXOs
value_sats = utxo.value_sats()
assert value_sats is not None
threshold = self.config.get('unconf_utxo_freeze_threshold', 5_000)
threshold = self.config.WALLET_UNCONF_UTXO_FREEZE_THRESHOLD_SAT
if value_sats >= threshold:
return False
# if funding tx has any is_mine input, then UTXO is fine
@ -2457,7 +2457,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
def get_request_URI(self, req: Request) -> Optional[str]:
lightning_invoice = None
if self.config.get('bip21_lightning', False):
if self.config.WALLET_BIP21_LIGHTNING:
lightning_invoice = self.get_bolt11_invoice(req)
return req.get_bip21_URI(lightning_invoice=lightning_invoice)
@ -2614,7 +2614,7 @@ class Abstract_Wallet(ABC, Logger, EventListener):
amount_msat=amount_msat,
message=req.message,
expiry=req.exp,
fallback_address=req.get_address() if self.config.get('bolt11_fallback', True) else None)
fallback_address=req.get_address() if self.config.WALLET_BOLT11_FALLBACK else None)
return invoice
def create_request(self, amount_sat: int, message: str, exp_delay: int, address: Optional[str]):

15
run_electrum

@ -341,8 +341,8 @@ def main():
config_options = {
'verbosity': '*' if util.is_android_debug_apk() else '',
'cmd': 'gui',
'gui': android_gui,
'single_password': True,
SimpleConfig.GUI_NAME.key(): android_gui,
SimpleConfig.WALLET_USE_SINGLE_PASSWORD.key(): True,
}
if util.get_android_package_name() == "org.electrum.testnet.electrum":
# ~hack for easier testnet builds. pkgname subject to change.
@ -394,8 +394,8 @@ def main():
# to not-yet-evaluated strings.
if cmdname == 'gui':
from electrum.gui.default_lang import get_default_language
gui_name = config.get('gui', 'qt')
lang = config.get('language')
gui_name = config.GUI_NAME
lang = config.LOCALIZATION_LANGUAGE
if not lang:
lang = get_default_language(gui_name=gui_name)
_logger.info(f"get_default_language: detected default as {lang=!r}")
@ -459,7 +459,7 @@ def handle_cmd(*, cmdname: str, config: 'SimpleConfig', config_options: dict):
configure_logging(config)
fd = daemon.get_file_descriptor(config)
if fd is not None:
plugins = init_plugins(config, config.get('gui', 'qt'))
plugins = init_plugins(config, config.GUI_NAME)
d = daemon.Daemon(config, fd, start_network=False)
try:
d.run_gui(config, plugins)
@ -490,10 +490,9 @@ def handle_cmd(*, cmdname: str, config: 'SimpleConfig', config_options: dict):
configure_logging(config, log_to_file=False) # don't spam logfiles for each client-side RPC, but support "-v"
cmd = known_commands[cmdname]
wallet_path = config.get_wallet_path()
if not config.get('offline'):
if not config.NETWORK_OFFLINE:
init_cmdline(config_options, wallet_path, True, config=config)
timeout = config.get('timeout', 60)
if timeout: timeout = int(timeout)
timeout = config.CLI_TIMEOUT
try:
result = daemon.request(config, 'run_cmdline', (config_options,), timeout)
except daemon.DaemonNotRunning:

Loading…
Cancel
Save