2015-04-02 10:00:07 +02:00
import json
2013-09-29 18:33:54 +02:00
import threading
2017-09-21 01:19:05 +02:00
import time
2013-09-29 18:33:54 +02:00
import os
2017-10-12 08:04:55 +02:00
import stat
2018-05-09 19:30:18 +02:00
from decimal import Decimal
2023-09-07 15:30:46 +00:00
from typing import Union , Optional , Dict , Sequence , Tuple , Any , Set , Callable
2018-07-14 18:45:02 +02:00
from numbers import Real
2023-05-24 17:41:44 +00:00
from functools import cached_property
2012-10-11 20:10:12 +02:00
2015-05-25 12:13:58 +09:00
from copy import deepcopy
2018-02-21 03:22:26 +01:00
2018-04-29 18:25:10 +02:00
from . import util
2018-10-18 12:41:47 +02:00
from . import constants
2023-05-24 17:41:44 +00:00
from . import invoices
2020-05-26 16:06:15 +02:00
from . util import base_units , base_unit_name_to_decimal_point , decimal_point_to_base_unit_name , UnknownBaseUnit , DECIMAL_POINT_DEFAULT
2022-02-25 09:33:24 +01:00
from . util import format_satoshis , format_fee_satoshis , os_chmod
2020-05-26 15:37:55 +02:00
from . util import user_dir , make_dir , NoDynamicFeeEstimates , quantize_feerate
2023-08-09 15:38:59 +00:00
from . lnutil import LN_MAX_FUNDING_SAT_LEGACY
2018-02-21 03:22:26 +01:00
from . i18n import _
2019-04-26 18:52:26 +02:00
from . logging import get_logger , Logger
2017-11-22 12:09:56 +01:00
FEE_ETA_TARGETS = [ 25 , 10 , 5 , 2 ]
2022-03-29 20:22:09 +02:00
FEE_DEPTH_TARGETS = [ 10_000_000 , 5_000_000 , 2_000_000 , 1_000_000 ,
800_000 , 600_000 , 400_000 , 250_000 , 100_000 ]
2018-07-31 14:36:42 +02:00
FEE_LN_ETA_TARGET = 2 # note: make sure the network is asking for estimates for this target
2017-01-09 09:22:17 +01:00
2018-02-21 03:22:26 +01:00
# satoshi per kbyte
FEERATE_MAX_DYNAMIC = 1500000
FEERATE_WARNING_HIGH_FEE = 600000
FEERATE_FALLBACK_STATIC_FEE = 150000
FEERATE_DEFAULT_RELAY = 1000
2019-09-18 22:08:19 +02:00
FEERATE_MAX_RELAY = 50000
2018-10-02 15:44:09 +02:00
FEERATE_STATIC_VALUES = [ 1000 , 2000 , 5000 , 10000 , 20000 , 30000 ,
50000 , 70000 , 100000 , 150000 , 200000 , 300000 ]
2018-02-21 03:22:26 +01:00
2021-09-24 20:13:58 +02:00
# The min feerate_per_kw that can be used in lightning so that
# the resulting onchain tx pays the min relay fee.
# This would be FEERATE_DEFAULT_RELAY / 4 if not for rounding errors,
# see https://github.com/ElementsProject/lightning/commit/2e687b9b352c9092b5e8bd4a688916ac50b44af0
FEERATE_PER_KW_MIN_RELAY_LIGHTNING = 253
2020-05-15 19:21:28 +02:00
FEE_RATIO_HIGH_WARNING = 0.05 # warn user if fee/amount for on-chain tx is higher than this
2018-02-21 03:22:26 +01:00
2023-08-10 08:11:37 +02:00
2019-04-26 18:52:26 +02:00
_logger = get_logger ( __name__ )
2014-06-25 17:34:51 +02:00
2018-07-02 22:52:20 +02:00
FINAL_CONFIG_VERSION = 3
2018-01-16 10:31:06 +01:00
2023-09-08 14:55:22 +00:00
_config_var_from_key = { } # type: Dict[str, 'ConfigVar']
2023-05-24 17:41:44 +00:00
class ConfigVar ( property ) :
2023-09-07 15:30:46 +00:00
def __init__ (
self ,
key : str ,
* ,
default : Union [ Any , Callable [ [ ' SimpleConfig ' ] , Any ] ] , # typically a literal, but can also be a callable
type_ = None ,
2023-09-18 11:00:52 +00:00
short_desc : Callable [ [ ] , str ] = None ,
long_desc : Callable [ [ ] , str ] = None ,
2023-09-07 15:30:46 +00:00
) :
2023-05-24 17:41:44 +00:00
self . _key = key
self . _default = default
self . _type = type_
2023-09-18 11:00:52 +00:00
# note: the descriptions are callables instead of str literals, to delay evaluating the _() translations
# until after the language is set.
assert short_desc is None or callable ( short_desc )
assert long_desc is None or callable ( long_desc )
self . _short_desc = short_desc
self . _long_desc = long_desc
2023-05-24 17:41:44 +00:00
property . __init__ ( self , self . _get_config_value , self . _set_config_value )
2023-09-08 15:09:17 +00:00
assert key not in _config_var_from_key , f " duplicate config key str: { key !r} "
2023-09-08 14:55:22 +00:00
_config_var_from_key [ key ] = self
2023-05-24 17:41:44 +00:00
def _get_config_value ( self , config : ' SimpleConfig ' ) :
2023-09-07 15:30:46 +00:00
with config . lock :
if config . is_set ( self . _key ) :
value = config . get ( self . _key )
if self . _type is not None :
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
else :
d = self . _default
value = d ( config ) if callable ( d ) else d
return value
2023-05-24 17:41:44 +00:00
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
2023-09-18 11:00:52 +00:00
def get_short_desc ( self ) - > Optional [ str ] :
desc = self . _short_desc
return desc ( ) if desc else None
def get_long_desc ( self ) - > Optional [ str ] :
desc = self . _long_desc
return desc ( ) if desc else None
2023-05-24 17:41:44 +00:00
def __repr__ ( self ) :
return f " <ConfigVar key= { self . _key !r} > "
2023-07-11 14:50:09 +00:00
def __deepcopy__ ( self , memo ) :
2023-09-08 15:09:17 +00:00
# We can be considered ~stateless. State is stored in the config, which is external.
return self
2023-07-11 14:50:09 +00:00
2023-05-24 17:41:44 +00:00
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 ( )
2023-09-18 11:00:52 +00:00
def get_short_desc ( self ) - > Optional [ str ] :
return self . _config_var . get_short_desc ( )
def get_long_desc ( self ) - > Optional [ str ] :
return self . _config_var . get_long_desc ( )
2023-05-24 17:41:44 +00:00
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} > "
2023-09-08 14:55:22 +00:00
def __eq__ ( self , other ) - > bool :
if not isinstance ( other , ConfigVarWithConfig ) :
return False
return self . _config is other . _config and self . _config_var is other . _config_var
2023-05-24 17:41:44 +00:00
2019-04-26 18:52:26 +02:00
class SimpleConfig ( Logger ) :
2012-11-19 13:56:25 +01:00
"""
2014-06-25 17:34:51 +02:00
The SimpleConfig class is responsible for handling operations involving
configuration files .
2018-01-16 10:31:06 +01:00
There are two different sources of possible configuration values :
2014-06-25 17:34:51 +02:00
1. Command line options .
2. User configuration ( in the user ' s config directory)
2018-01-16 10:31:06 +01:00
They are taken in order ( 1. overrides config options set in 2. )
2014-06-25 17:34:51 +02:00
"""
2017-10-21 11:15:59 +02:00
2018-01-16 10:31:06 +01:00
def __init__ ( self , options = None , read_user_config_function = None ,
read_user_dir_function = None ) :
if options is None :
options = { }
2014-06-25 17:34:51 +02:00
2019-04-26 18:52:26 +02:00
Logger . __init__ ( self )
2019-01-23 15:10:11 +01:00
2014-06-25 17:34:51 +02:00
# This lock needs to be acquired for updating and reading the config in
# a thread-safe way.
self . lock = threading . RLock ( )
2020-10-27 18:55:39 +01:00
self . mempool_fees = None # type: Optional[Sequence[Tuple[Union[float, int], int]]]
2021-04-15 19:00:46 +02:00
self . fee_estimates = { } # type: Dict[int, int]
2017-09-21 01:19:05 +02:00
self . last_time_fee_estimates_requested = 0 # zero ensures immediate fees
2017-01-09 09:22:17 +01:00
2014-06-25 17:34:51 +02:00
# The following two functions are there for dependency injection when
# testing.
if read_user_config_function is None :
read_user_config_function = read_user_config
if read_user_dir_function is None :
self . user_dir = user_dir
else :
self . user_dir = read_user_dir_function
2015-05-25 12:13:58 +09:00
# The command line options
self . cmdline_options = deepcopy ( options )
2018-01-16 10:31:06 +01:00
# don't allow to be set on CLI:
self . cmdline_options . pop ( ' config_version ' , None )
2015-05-25 12:13:58 +09:00
# Set self.path and read the user config
self . user_config = { } # for self.get in electrum_path()
self . path = self . electrum_path ( )
2014-06-25 17:34:51 +02:00
self . user_config = read_user_config_function ( self . path )
2018-01-16 10:31:06 +01:00
if not self . user_config :
# avoid new config getting upgraded
self . user_config = { ' config_version ' : FINAL_CONFIG_VERSION }
2023-05-24 17:41:44 +00:00
self . _not_modifiable_keys = set ( ) # type: Set[str]
2020-04-24 17:17:12 +02:00
2018-01-16 10:31:06 +01:00
# config "upgrade" - CLI options
self . rename_config_keys (
self . cmdline_options , { ' auto_cycle ' : ' auto_connect ' } , True )
# config upgrade - user config
if self . requires_upgrade ( ) :
self . upgrade ( )
2020-04-24 17:17:12 +02:00
self . _check_dependent_keys ( )
2020-05-26 15:37:55 +02:00
# units and formatting
2023-05-24 17:41:44 +00:00
# FIXME is this duplication (dp, nz, post_sat, thou_sep) due to performance reasons??
self . decimal_point = self . BTC_AMOUNTS_DECIMAL_POINT
2020-05-26 15:37:55 +02:00
try :
decimal_point_to_base_unit_name ( self . decimal_point )
except UnknownBaseUnit :
self . decimal_point = DECIMAL_POINT_DEFAULT
2023-05-24 17:41:44 +00:00
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
2020-05-26 15:37:55 +02:00
2015-05-25 12:13:58 +09:00
def electrum_path ( self ) :
2018-01-16 10:31:06 +01:00
# Read electrum_path from command line
2015-05-25 12:13:58 +09:00
# Otherwise use the user's default data directory.
path = self . get ( ' electrum_path ' )
if path is None :
path = self . user_dir ( )
2013-10-08 13:10:38 +02:00
2018-05-28 14:22:54 +02:00
make_dir ( path , allow_symlink = False )
2017-01-07 16:58:23 +01:00
if self . get ( ' testnet ' ) :
path = os . path . join ( path , ' testnet ' )
2018-05-28 14:22:54 +02:00
make_dir ( path , allow_symlink = False )
2018-04-11 20:10:14 +03:00
elif self . get ( ' regtest ' ) :
path = os . path . join ( path , ' regtest ' )
2018-05-28 14:22:54 +02:00
make_dir ( path , allow_symlink = False )
2018-06-22 17:07:07 +02:00
elif self . get ( ' simnet ' ) :
path = os . path . join ( path , ' simnet ' )
make_dir ( path , allow_symlink = False )
2021-05-02 05:54:58 +09:00
elif self . get ( ' signet ' ) :
path = os . path . join ( path , ' signet ' )
make_dir ( path , allow_symlink = False )
2013-09-01 15:26:52 +02:00
2019-04-26 18:52:26 +02:00
self . logger . info ( f " electrum directory { path } " )
2015-05-25 12:13:58 +09:00
return path
2012-12-17 15:08:34 +01:00
2018-01-16 10:31:06 +01:00
def rename_config_keys ( self , config , keypairs , deprecation_warning = False ) :
""" Migrate old key names to new ones """
2015-05-25 16:18:52 +09:00
updated = False
2017-01-22 21:25:24 +03:00
for old_key , new_key in keypairs . items ( ) :
2015-05-25 16:18:52 +09:00
if old_key in config :
2018-01-16 10:31:06 +01:00
if new_key not in config :
2015-05-25 16:18:52 +09:00
config [ new_key ] = config [ old_key ]
2018-01-16 10:31:06 +01:00
if deprecation_warning :
2019-04-26 18:52:26 +02:00
self . logger . warning ( ' Note that the {} variable has been deprecated. '
' You should use {} instead. ' . format ( old_key , new_key ) )
2015-05-25 16:18:52 +09:00
del config [ old_key ]
updated = True
return updated
2023-05-24 17:41:44 +00:00
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
2014-06-25 17:34:51 +02:00
if not self . is_modifiable ( key ) :
2019-04-26 18:52:26 +02:00
self . logger . warning ( f " not changing config key ' { key } ' set on the command line " )
2012-10-11 20:10:12 +02:00
return
2018-10-21 14:58:55 +02:00
try :
json . dumps ( key )
json . dumps ( value )
2023-04-23 01:33:12 +00:00
except Exception :
2019-04-26 18:52:26 +02:00
self . logger . info ( f " json error: cannot save { repr ( key ) } ( { repr ( value ) } ) " )
2018-10-21 14:58:55 +02:00
return
2023-05-23 13:45:47 +00:00
self . _set_key_in_user_config ( key , value , save = save )
2012-10-11 20:10:12 +02:00
2023-05-24 17:41:44 +00:00
def _set_key_in_user_config ( self , key : str , value , * , save = True ) - > None :
assert isinstance ( key , str ) , key
2014-06-25 17:34:51 +02:00
with self . lock :
2018-01-16 10:31:06 +01:00
if value is not None :
self . user_config [ key ] = value
else :
self . user_config . pop ( key , None )
2014-06-25 17:34:51 +02:00
if save :
self . save_user_config ( )
2012-10-11 20:10:12 +02:00
2023-05-24 17:41:44 +00:00
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
2014-06-25 17:34:51 +02:00
with self . lock :
2015-05-25 12:13:58 +09:00
out = self . cmdline_options . get ( key )
2015-01-30 10:19:22 +01:00
if out is None :
2018-01-16 10:31:06 +01:00
out = self . user_config . get ( key , default )
2012-10-11 20:10:12 +02:00
return out
2023-05-24 17:41:44 +00:00
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 . . .
2020-04-24 17:17:12 +02:00
def _check_dependent_keys ( self ) - > None :
2023-05-24 17:41:44 +00:00
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 )
2020-04-24 17:17:12 +02:00
2018-01-16 10:31:06 +01:00
def requires_upgrade ( self ) :
return self . get_config_version ( ) < FINAL_CONFIG_VERSION
def upgrade ( self ) :
with self . lock :
2019-04-26 18:52:26 +02:00
self . logger . info ( ' upgrading config ' )
2018-01-16 10:31:06 +01:00
self . convert_version_2 ( )
2018-07-02 22:52:20 +02:00
self . convert_version_3 ( )
2018-01-16 10:31:06 +01:00
self . set_key ( ' config_version ' , FINAL_CONFIG_VERSION , save = True )
def convert_version_2 ( self ) :
if not self . _is_upgrade_method_needed ( 1 , 1 ) :
return
self . rename_config_keys ( self . user_config , { ' auto_cycle ' : ' auto_connect ' } )
try :
2018-01-16 11:53:31 +01:00
# change server string FROM host:port:proto TO host:port:s
2018-01-16 10:31:06 +01:00
server_str = self . user_config . get ( ' server ' )
host , port , protocol = str ( server_str ) . rsplit ( ' : ' , 2 )
assert protocol in ( ' s ' , ' t ' )
int ( port ) # Throw if cannot be converted to int
2018-01-16 11:53:31 +01:00
server_str = ' {} : {} :s ' . format ( host , port )
2018-01-16 10:31:06 +01:00
self . _set_key_in_user_config ( ' server ' , server_str )
except BaseException :
self . _set_key_in_user_config ( ' server ' , None )
self . set_key ( ' config_version ' , 2 )
2018-07-02 22:52:20 +02:00
def convert_version_3 ( self ) :
if not self . _is_upgrade_method_needed ( 2 , 2 ) :
return
base_unit = self . user_config . get ( ' base_unit ' )
if isinstance ( base_unit , str ) :
self . _set_key_in_user_config ( ' base_unit ' , None )
map_ = { ' btc ' : 8 , ' mbtc ' : 5 , ' ubtc ' : 2 , ' bits ' : 2 , ' sat ' : 0 }
decimal_point = map_ . get ( base_unit . lower ( ) )
self . _set_key_in_user_config ( ' decimal_point ' , decimal_point )
self . set_key ( ' config_version ' , 3 )
2018-01-16 10:31:06 +01:00
def _is_upgrade_method_needed ( self , min_version , max_version ) :
cur_version = self . get_config_version ( )
if cur_version > max_version :
return False
elif cur_version < min_version :
2018-04-07 17:10:30 +02:00
raise Exception (
2018-01-16 10:31:06 +01:00
( ' config upgrade: unexpected version %d (should be %d - %d ) '
% ( cur_version , min_version , max_version ) ) )
else :
return True
def get_config_version ( self ) :
config_version = self . get ( ' config_version ' , 1 )
if config_version > FINAL_CONFIG_VERSION :
2019-04-26 18:52:26 +02:00
self . logger . warning ( ' config version ( {} ) is higher than latest ( {} ) '
. format ( config_version , FINAL_CONFIG_VERSION ) )
2018-01-16 10:31:06 +01:00
return config_version
2023-05-24 17:41:44 +00:00
def is_modifiable ( self , key : Union [ str , ConfigVar , ConfigVarWithConfig ] ) - > bool :
if isinstance ( key , ( ConfigVar , ConfigVarWithConfig ) ) :
key = key . key ( )
2020-04-24 17:17:12 +02:00
return ( key not in self . cmdline_options
and key not in self . _not_modifiable_keys )
2023-05-24 17:41:44 +00:00
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
2020-04-24 17:17:12 +02:00
self . _not_modifiable_keys . add ( key )
2012-10-11 20:10:12 +02:00
2012-10-12 14:44:56 +02:00
def save_user_config ( self ) :
2023-05-24 17:41:44 +00:00
if self . CONFIG_FORGET_CHANGES :
2020-01-21 04:54:34 +01:00
return
2015-04-02 10:00:07 +02:00
if not self . path :
return
2013-09-02 15:05:33 +02:00
path = os . path . join ( self . path , " config " )
2015-04-02 10:00:07 +02:00
s = json . dumps ( self . user_config , indent = 4 , sort_keys = True )
2018-03-19 01:04:02 +01:00
try :
2018-03-23 21:47:51 +01:00
with open ( path , " w " , encoding = ' utf-8 ' ) as f :
2018-03-19 01:04:02 +01:00
f . write ( s )
2022-02-25 09:33:24 +01:00
os_chmod ( path , stat . S_IREAD | stat . S_IWRITE )
2023-11-02 16:59:53 +00:00
except OSError :
# datadir probably deleted while running... e.g. portable exe running on ejected USB drive
# (in which case it is typically either FileNotFoundError or PermissionError,
# but let's just catch the more generic OSError and test explicitly)
2018-03-19 01:04:02 +01:00
if os . path . exists ( self . path ) : # or maybe not?
raise
2014-06-25 17:34:51 +02:00
2023-05-24 17:41:44 +00:00
def get_backup_dir ( self ) - > Optional [ str ] :
2022-03-24 14:55:37 +01:00
# this is used to save wallet file backups (without active lightning channels)
2021-03-23 17:25:27 +01:00
# on Android, the export backup button uses android_backup_dir()
if ' ANDROID_DATA ' in os . environ :
return None
else :
2023-05-24 17:41:44 +00:00
return self . WALLET_BACKUP_DIRECTORY
2021-03-23 17:25:27 +01:00
2019-09-10 17:10:52 +02:00
def get_wallet_path ( self , * , use_gui_last_wallet = False ) :
2015-05-08 10:58:54 +02:00
""" Set the path of the wallet. """
# command line -w option
2016-02-24 16:57:58 +01:00
if self . get ( ' wallet_path ' ) :
2018-09-06 15:14:35 +02:00
return os . path . join ( self . get ( ' cwd ' , ' ' ) , self . get ( ' wallet_path ' ) )
2015-05-08 10:58:54 +02:00
2019-09-10 17:10:52 +02:00
if use_gui_last_wallet :
2023-05-24 17:41:44 +00:00
path = self . GUI_LAST_WALLET
2019-09-10 17:10:52 +02:00
if path and os . path . exists ( path ) :
return path
2015-05-08 10:58:54 +02:00
qt gui: more resilient startup: catch more exceptions, better fallback
fixes https://github.com/spesmilo/electrum/issues/7447
Consider this trace for 4.2.0:
```
Traceback (most recent call last):
File "electrum/gui/qt/__init__.py", line 332, in start_new_window
File "electrum/gui/qt/__init__.py", line 363, in _start_wizard_to_select_or_create_wallet
File "electrum/gui/qt/installwizard.py", line 302, in select_storage
File "electrum/util.py", line 504, in get_new_wallet_name
PermissionError: [Errno 1] Operation not permitted: '/Users/admin/Documents/Peach/MS'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "electrum/gui/qt/__init__.py", line 426, in main
File "electrum/gui/qt/__init__.py", line 307, in wrapper
File "electrum/gui/qt/__init__.py", line 349, in start_new_window
File "electrum/util.py", line 504, in get_new_wallet_name
PermissionError: [Errno 1] Operation not permitted: '/Users/admin/Documents/Peach/MS'
```
Note that `get_new_wallet_name` (os.listdir) can raise OSError,
and we were calling that on the main entrypoint codepath without exception-handling.
We were also calling it in the fallback codepath without exception-handling.
i.e. the GUI errored out on every startup for affected users, and without CLI usage
it was not possible to recover.
2022-03-23 03:58:33 +01:00
new_path = self . get_fallback_wallet_path ( )
2015-05-08 10:58:54 +02:00
2023-10-13 13:10:56 +02:00
# TODO: this can be removed by now
2015-05-08 10:58:54 +02:00
# default path in pre 1.9 versions
old_path = os . path . join ( self . path , " electrum.dat " )
if os . path . exists ( old_path ) and not os . path . exists ( new_path ) :
os . rename ( old_path , new_path )
return new_path
2023-10-13 13:10:56 +02:00
def get_datadir_wallet_path ( self ) :
qt gui: more resilient startup: catch more exceptions, better fallback
fixes https://github.com/spesmilo/electrum/issues/7447
Consider this trace for 4.2.0:
```
Traceback (most recent call last):
File "electrum/gui/qt/__init__.py", line 332, in start_new_window
File "electrum/gui/qt/__init__.py", line 363, in _start_wizard_to_select_or_create_wallet
File "electrum/gui/qt/installwizard.py", line 302, in select_storage
File "electrum/util.py", line 504, in get_new_wallet_name
PermissionError: [Errno 1] Operation not permitted: '/Users/admin/Documents/Peach/MS'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "electrum/gui/qt/__init__.py", line 426, in main
File "electrum/gui/qt/__init__.py", line 307, in wrapper
File "electrum/gui/qt/__init__.py", line 349, in start_new_window
File "electrum/util.py", line 504, in get_new_wallet_name
PermissionError: [Errno 1] Operation not permitted: '/Users/admin/Documents/Peach/MS'
```
Note that `get_new_wallet_name` (os.listdir) can raise OSError,
and we were calling that on the main entrypoint codepath without exception-handling.
We were also calling it in the fallback codepath without exception-handling.
i.e. the GUI errored out on every startup for affected users, and without CLI usage
it was not possible to recover.
2022-03-23 03:58:33 +01:00
util . assert_datadir_available ( self . path )
dirpath = os . path . join ( self . path , " wallets " )
make_dir ( dirpath , allow_symlink = False )
2023-10-13 13:10:56 +02:00
return dirpath
def get_fallback_wallet_path ( self ) :
return os . path . join ( self . get_datadir_wallet_path ( ) , " default_wallet " )
qt gui: more resilient startup: catch more exceptions, better fallback
fixes https://github.com/spesmilo/electrum/issues/7447
Consider this trace for 4.2.0:
```
Traceback (most recent call last):
File "electrum/gui/qt/__init__.py", line 332, in start_new_window
File "electrum/gui/qt/__init__.py", line 363, in _start_wizard_to_select_or_create_wallet
File "electrum/gui/qt/installwizard.py", line 302, in select_storage
File "electrum/util.py", line 504, in get_new_wallet_name
PermissionError: [Errno 1] Operation not permitted: '/Users/admin/Documents/Peach/MS'
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "electrum/gui/qt/__init__.py", line 426, in main
File "electrum/gui/qt/__init__.py", line 307, in wrapper
File "electrum/gui/qt/__init__.py", line 349, in start_new_window
File "electrum/util.py", line 504, in get_new_wallet_name
PermissionError: [Errno 1] Operation not permitted: '/Users/admin/Documents/Peach/MS'
```
Note that `get_new_wallet_name` (os.listdir) can raise OSError,
and we were calling that on the main entrypoint codepath without exception-handling.
We were also calling it in the fallback codepath without exception-handling.
i.e. the GUI errored out on every startup for affected users, and without CLI usage
it was not possible to recover.
2022-03-23 03:58:33 +01:00
2015-12-31 11:36:33 +09:00
def remove_from_recently_open ( self , filename ) :
2023-05-24 17:41:44 +00:00
recent = self . RECENTLY_OPEN_WALLET_FILES or [ ]
2015-12-31 11:36:33 +09:00
if filename in recent :
recent . remove ( filename )
2023-05-24 17:41:44 +00:00
self . RECENTLY_OPEN_WALLET_FILES = recent
2015-05-08 10:58:54 +02:00
2016-02-10 21:51:22 +09:00
def set_session_timeout ( self , seconds ) :
2019-04-26 18:52:26 +02:00
self . logger . info ( f " session timeout -> { seconds } seconds " )
2023-05-24 17:41:44 +00:00
self . HWD_SESSION_TIMEOUT = seconds
2016-02-10 21:51:22 +09:00
def get_session_timeout ( self ) :
2023-05-24 17:41:44 +00:00
return self . HWD_SESSION_TIMEOUT
2016-02-10 21:51:22 +09:00
2016-03-08 11:10:04 +01:00
def save_last_wallet ( self , wallet ) :
if self . get ( ' wallet_path ' ) is None :
path = wallet . storage . path
2023-05-24 17:41:44 +00:00
self . GUI_LAST_WALLET = path
2016-03-08 11:10:04 +01:00
2018-02-21 03:22:26 +01:00
def impose_hard_limits_on_fee ( func ) :
def get_fee_within_limits ( self , * args , * * kwargs ) :
fee = func ( self , * args , * * kwargs )
if fee is None :
return fee
fee = min ( FEERATE_MAX_DYNAMIC , fee )
fee = max ( FEERATE_DEFAULT_RELAY , fee )
return fee
return get_fee_within_limits
2018-07-30 19:15:05 +02:00
def eta_to_fee ( self , slider_pos ) - > Optional [ int ] :
2018-02-21 03:22:26 +01:00
""" Returns fee in sat/kbyte. """
2018-06-15 17:02:44 +02:00
slider_pos = max ( slider_pos , 0 )
slider_pos = min ( slider_pos , len ( FEE_ETA_TARGETS ) )
if slider_pos < len ( FEE_ETA_TARGETS ) :
2021-01-18 08:05:29 +01:00
num_blocks = FEE_ETA_TARGETS [ int ( slider_pos ) ]
2018-07-30 19:15:05 +02:00
fee = self . eta_target_to_fee ( num_blocks )
2017-01-09 09:22:17 +01:00
else :
2018-07-30 19:15:05 +02:00
fee = self . eta_target_to_fee ( 1 )
return fee
@impose_hard_limits_on_fee
def eta_target_to_fee ( self , num_blocks : int ) - > Optional [ int ] :
""" Returns fee in sat/kbyte. """
if num_blocks == 1 :
2017-01-09 09:22:17 +01:00
fee = self . fee_estimates . get ( 2 )
if fee is not None :
2018-07-30 19:15:05 +02:00
fee + = fee / 2
2018-06-15 17:02:44 +02:00
fee = int ( fee )
2018-07-30 19:15:05 +02:00
else :
fee = self . fee_estimates . get ( num_blocks )
2020-10-15 19:50:59 +02:00
if fee is not None :
fee = int ( fee )
2017-01-09 09:22:17 +01:00
return fee
2020-10-27 18:55:39 +01:00
def fee_to_depth ( self , target_fee : Real ) - > Optional [ int ] :
2018-07-14 18:45:02 +02:00
""" For a given sat/vbyte fee, returns an estimate of how deep
it would be in the current mempool in vbytes .
Pessimistic == overestimates the depth .
"""
2020-10-27 18:55:39 +01:00
if self . mempool_fees is None :
return None
2017-11-22 12:09:56 +01:00
depth = 0
for fee , s in self . mempool_fees :
depth + = s
2018-02-07 17:30:08 +01:00
if fee < = target_fee :
2017-11-22 12:09:56 +01:00
break
return depth
2020-10-27 18:55:39 +01:00
def depth_to_fee ( self , slider_pos ) - > Optional [ int ] :
2018-02-21 03:22:26 +01:00
""" Returns fee in sat/kbyte. """
2018-06-15 17:02:44 +02:00
target = self . depth_target ( slider_pos )
2018-07-14 18:45:02 +02:00
return self . depth_target_to_fee ( target )
@impose_hard_limits_on_fee
2020-10-27 18:55:39 +01:00
def depth_target_to_fee ( self , target : int ) - > Optional [ int ] :
2018-07-14 18:45:02 +02:00
""" Returns fee in sat/kbyte.
2018-07-14 18:54:27 +02:00
target : desired mempool depth in vbytes
2018-07-14 18:45:02 +02:00
"""
2020-10-27 18:55:39 +01:00
if self . mempool_fees is None :
return None
2017-11-22 12:09:56 +01:00
depth = 0
for fee , s in self . mempool_fees :
depth + = s
if depth > target :
break
else :
return 0
2022-03-29 20:22:09 +02:00
# add one sat/byte as currently that is the max precision of the histogram
# note: precision depends on server.
# old ElectrumX <1.16 has 1 s/b prec, >=1.16 has 0.1 s/b prec.
# electrs seems to use untruncated double-precision floating points.
# # TODO decrease this to 0.1 s/b next time we bump the required protocol version
2018-07-14 18:45:02 +02:00
fee + = 1
# convert to sat/kbyte
2020-10-15 19:50:59 +02:00
return int ( fee * 1000 )
2017-11-22 12:09:56 +01:00
2020-11-04 01:49:57 +01:00
def depth_target ( self , slider_pos : int ) - > int :
""" Returns mempool depth target in bytes for a fee slider position. """
2018-06-15 17:02:44 +02:00
slider_pos = max ( slider_pos , 0 )
slider_pos = min ( slider_pos , len ( FEE_DEPTH_TARGETS ) - 1 )
return FEE_DEPTH_TARGETS [ slider_pos ]
2017-11-22 12:09:56 +01:00
2020-11-04 01:49:57 +01:00
def eta_target ( self , slider_pos : int ) - > int :
""" Returns ' num blocks ' ETA target for a fee slider position. """
if slider_pos == len ( FEE_ETA_TARGETS ) :
2018-02-21 03:22:26 +01:00
return 1
2020-11-04 01:49:57 +01:00
return FEE_ETA_TARGETS [ slider_pos ]
2017-11-22 12:09:56 +01:00
2021-04-15 18:50:54 +02:00
def fee_to_eta ( self , fee_per_kb : Optional [ int ] ) - > int :
2020-11-04 01:49:57 +01:00
""" Returns ' num blocks ' ETA estimate for given fee rate,
or - 1 for low fee .
"""
2017-01-09 09:22:17 +01:00
import operator
2021-04-15 18:50:54 +02:00
lst = list ( self . fee_estimates . items ( ) )
next_block_fee = self . eta_target_to_fee ( 1 )
if next_block_fee is not None :
lst + = [ ( 1 , next_block_fee ) ]
if not lst or fee_per_kb is None :
return - 1
2020-11-04 01:49:57 +01:00
dist = map ( lambda x : ( x [ 0 ] , abs ( x [ 1 ] - fee_per_kb ) ) , lst )
2017-01-09 09:22:17 +01:00
min_target , min_value = min ( dist , key = operator . itemgetter ( 1 ) )
2020-11-04 01:49:57 +01:00
if fee_per_kb < self . fee_estimates . get ( FEE_ETA_TARGETS [ 0 ] ) / 2 :
2017-01-09 09:22:17 +01:00
min_target = - 1
return min_target
2022-03-31 22:50:31 +02:00
def get_depth_mb_str ( self , depth : int ) - > str :
# e.g. 500_000 -> "0.50 MB"
depth_mb = " {:.2f} " . format ( depth / 1_000_000 ) # maybe .rstrip("0") ?
return f " { depth_mb } MB "
2020-10-27 18:55:39 +01:00
def depth_tooltip ( self , depth : Optional [ int ] ) - > str :
""" Returns text tooltip for given mempool depth (in vbytes). """
if depth is None :
return " unknown from tip "
2022-03-31 22:50:31 +02:00
depth_mb = self . get_depth_mb_str ( depth )
return _ ( " {} from tip " ) . format ( depth_mb )
2017-11-22 12:09:56 +01:00
def eta_tooltip ( self , x ) :
2018-02-21 03:22:26 +01:00
if x < 0 :
return _ ( ' Low fee ' )
elif x == 1 :
return _ ( ' In the next block ' )
else :
return _ ( ' Within {} blocks ' ) . format ( x )
2017-11-22 12:09:56 +01:00
2021-01-19 14:15:07 +01:00
def get_fee_target ( self ) :
2017-11-22 12:09:56 +01:00
dyn = self . is_dynfee ( )
2018-02-13 00:03:42 +01:00
mempool = self . use_mempool_fees ( )
2018-02-06 11:09:42 +01:00
pos = self . get_depth_level ( ) if mempool else self . get_fee_level ( )
2017-11-22 12:09:56 +01:00
fee_rate = self . fee_per_kb ( )
target , tooltip = self . get_fee_text ( pos , dyn , mempool , fee_rate )
2021-01-19 14:15:07 +01:00
return target , tooltip , dyn
def get_fee_status ( self ) :
target , tooltip , dyn = self . get_fee_target ( )
2018-03-05 09:49:17 +01:00
return tooltip + ' [ %s ] ' % target if dyn else target + ' [Static] '
2017-11-22 12:09:56 +01:00
2020-11-04 01:49:57 +01:00
def get_fee_text (
self ,
slider_pos : int ,
dyn : bool ,
mempool : bool ,
fee_per_kb : Optional [ int ] ,
) :
2018-02-21 03:22:26 +01:00
""" Returns (text, tooltip) where
text is what we target : static fee / num blocks to confirm in / mempool depth
tooltip is the corresponding estimate ( e . g . num blocks for a static fee )
2019-02-18 17:52:50 +01:00
fee_rate is in sat / kbyte
2018-02-21 03:22:26 +01:00
"""
2020-11-04 01:49:57 +01:00
if fee_per_kb is None :
2018-04-24 12:54:14 -04:00
rate_str = ' unknown '
2020-11-04 01:49:57 +01:00
fee_per_byte = None
2018-04-24 12:54:14 -04:00
else :
2020-11-04 01:49:57 +01:00
fee_per_byte = fee_per_kb / 1000
rate_str = format_fee_satoshis ( fee_per_byte ) + ' sat/byte '
2018-04-24 12:54:14 -04:00
2017-11-22 12:09:56 +01:00
if dyn :
if mempool :
2020-11-04 01:49:57 +01:00
depth = self . depth_target ( slider_pos )
2017-11-22 12:09:56 +01:00
text = self . depth_tooltip ( depth )
else :
2020-11-04 01:49:57 +01:00
eta = self . eta_target ( slider_pos )
2017-11-22 12:09:56 +01:00
text = self . eta_tooltip ( eta )
tooltip = rate_str
2020-11-04 01:49:57 +01:00
else : # using static fees
assert fee_per_kb is not None
assert fee_per_byte is not None
2017-11-22 12:09:56 +01:00
text = rate_str
2018-02-21 03:22:26 +01:00
if mempool and self . has_fee_mempool ( ) :
2020-11-04 01:49:57 +01:00
depth = self . fee_to_depth ( fee_per_byte )
2018-02-21 03:22:26 +01:00
tooltip = self . depth_tooltip ( depth )
elif not mempool and self . has_fee_etas ( ) :
2020-11-04 01:49:57 +01:00
eta = self . fee_to_eta ( fee_per_kb )
2018-02-21 03:22:26 +01:00
tooltip = self . eta_tooltip ( eta )
2017-11-22 12:09:56 +01:00
else :
2018-02-21 03:22:26 +01:00
tooltip = ' '
2017-11-22 12:09:56 +01:00
return text , tooltip
2023-05-24 17:41:44 +00:00
def get_depth_level ( self ) - > int :
2018-02-06 11:09:42 +01:00
maxp = len ( FEE_DEPTH_TARGETS ) - 1
2023-05-24 17:41:44 +00:00
return min ( maxp , self . FEE_EST_DYNAMIC_MEMPOOL_SLIDERPOS )
2018-02-06 11:09:42 +01:00
2023-05-24 17:41:44 +00:00
def get_fee_level ( self ) - > int :
2018-02-21 03:22:26 +01:00
maxp = len ( FEE_ETA_TARGETS ) # not (-1) to have "next block"
2023-05-24 17:41:44 +00:00
return min ( maxp , self . FEE_EST_DYNAMIC_ETA_SLIDERPOS )
2018-02-06 11:09:42 +01:00
2020-10-27 18:55:39 +01:00
def get_fee_slider ( self , dyn , mempool ) - > Tuple [ int , int , Optional [ int ] ] :
2017-11-22 12:09:56 +01:00
if dyn :
if mempool :
2018-02-06 11:09:42 +01:00
pos = self . get_depth_level ( )
2017-11-22 12:09:56 +01:00
maxp = len ( FEE_DEPTH_TARGETS ) - 1
fee_rate = self . depth_to_fee ( pos )
else :
2018-02-06 11:09:42 +01:00
pos = self . get_fee_level ( )
2018-02-21 03:22:26 +01:00
maxp = len ( FEE_ETA_TARGETS ) # not (-1) to have "next block"
2017-11-22 12:09:56 +01:00
fee_rate = self . eta_to_fee ( pos )
else :
2018-04-12 12:17:24 +02:00
fee_rate = self . fee_per_kb ( dyn = False )
2017-11-22 12:09:56 +01:00
pos = self . static_fee_index ( fee_rate )
2018-10-02 15:44:09 +02:00
maxp = len ( FEERATE_STATIC_VALUES ) - 1
2017-11-22 12:09:56 +01:00
return maxp , pos , fee_rate
2017-10-21 11:15:59 +02:00
def static_fee ( self , i ) :
2018-02-21 03:22:26 +01:00
return FEERATE_STATIC_VALUES [ i ]
2017-10-21 11:15:59 +02:00
2021-04-15 19:00:46 +02:00
def static_fee_index ( self , fee_per_kb : Optional [ int ] ) - > int :
if fee_per_kb is None :
2018-04-12 12:17:24 +02:00
raise TypeError ( ' static fee cannot be None ' )
2021-04-15 19:00:46 +02:00
dist = list ( map ( lambda x : abs ( x - fee_per_kb ) , FEERATE_STATIC_VALUES ) )
2017-10-21 11:15:59 +02:00
return min ( range ( len ( dist ) ) , key = dist . __getitem__ )
2017-11-22 12:09:56 +01:00
def has_fee_etas ( self ) :
return len ( self . fee_estimates ) == 4
2020-10-27 18:55:39 +01:00
def has_fee_mempool ( self ) - > bool :
return self . mempool_fees is not None
2017-01-09 09:22:17 +01:00
2018-02-21 03:22:26 +01:00
def has_dynamic_fees_ready ( self ) :
if self . use_mempool_fees ( ) :
return self . has_fee_mempool ( )
else :
return self . has_fee_etas ( )
2023-05-24 17:41:44 +00:00
def is_dynfee ( self ) - > bool :
return self . FEE_EST_DYNAMIC
2017-01-09 09:22:17 +01:00
2023-05-24 17:41:44 +00:00
def use_mempool_fees ( self ) - > bool :
return self . FEE_EST_USE_MEMPOOL
2017-11-22 12:09:56 +01:00
2018-06-15 17:02:44 +02:00
def _feerate_from_fractional_slider_position ( self , fee_level : float , dyn : bool ,
mempool : bool ) - > Union [ int , None ] :
fee_level = max ( fee_level , 0 )
fee_level = min ( fee_level , 1 )
if dyn :
max_pos = ( len ( FEE_DEPTH_TARGETS ) - 1 ) if mempool else len ( FEE_ETA_TARGETS )
slider_pos = round ( fee_level * max_pos )
fee_rate = self . depth_to_fee ( slider_pos ) if mempool else self . eta_to_fee ( slider_pos )
else :
max_pos = len ( FEERATE_STATIC_VALUES ) - 1
slider_pos = round ( fee_level * max_pos )
fee_rate = FEERATE_STATIC_VALUES [ slider_pos ]
return fee_rate
2020-10-15 19:50:59 +02:00
def fee_per_kb ( self , dyn : bool = None , mempool : bool = None , fee_level : float = None ) - > Optional [ int ] :
2018-01-03 18:08:10 +01:00
""" Returns sat/kvB fee to pay for a txn.
Note : might return None .
2018-06-15 17:02:44 +02:00
fee_level : float between 0.0 and 1.0 , representing fee slider position
2018-01-03 18:08:10 +01:00
"""
2018-10-18 12:41:47 +02:00
if constants . net is constants . BitcoinRegtest :
2023-10-24 12:22:36 +02:00
return self . FEE_EST_STATIC_FEERATE
2018-04-12 12:17:24 +02:00
if dyn is None :
dyn = self . is_dynfee ( )
if mempool is None :
mempool = self . use_mempool_fees ( )
2018-06-15 17:02:44 +02:00
if fee_level is not None :
return self . _feerate_from_fractional_slider_position ( fee_level , dyn , mempool )
# there is no fee_level specified; will use config.
# note: 'depth_level' and 'fee_level' in config are integer slider positions,
# unlike fee_level here, which (when given) is a float in [0.0, 1.0]
2018-04-12 12:17:24 +02:00
if dyn :
if mempool :
2018-02-06 11:09:42 +01:00
fee_rate = self . depth_to_fee ( self . get_depth_level ( ) )
2017-11-22 12:09:56 +01:00
else :
2018-02-06 11:09:42 +01:00
fee_rate = self . eta_to_fee ( self . get_fee_level ( ) )
2017-01-09 09:22:17 +01:00
else :
2023-10-08 17:43:42 +02:00
fee_rate = self . FEE_EST_STATIC_FEERATE
2020-10-15 19:50:59 +02:00
if fee_rate is not None :
fee_rate = int ( fee_rate )
2017-01-09 09:22:17 +01:00
return fee_rate
2015-05-08 10:58:54 +02:00
2023-10-10 12:11:45 +00:00
def getfeerate ( self ) - > Tuple [ str , int , Optional [ int ] , str ] :
2023-10-08 17:43:42 +02:00
dyn = self . is_dynfee ( )
mempool = self . use_mempool_fees ( )
if dyn :
if mempool :
method = ' mempool '
fee_level = self . get_depth_level ( )
value = self . depth_target ( fee_level )
fee_rate = self . depth_to_fee ( fee_level )
tooltip = self . depth_tooltip ( value )
else :
method = ' ETA '
fee_level = self . get_fee_level ( )
value = self . eta_target ( fee_level )
fee_rate = self . eta_to_fee ( fee_level )
tooltip = self . eta_tooltip ( value )
else :
method = ' static '
value = self . FEE_EST_STATIC_FEERATE
fee_rate = value
tooltip = ' static feerate '
return method , value , fee_rate , tooltip
def setfeerate ( self , fee_method : str , value : int ) :
if fee_method == ' mempool ' :
2023-10-09 12:19:23 +02:00
if value not in FEE_DEPTH_TARGETS :
2023-10-08 17:43:42 +02:00
raise Exception ( f " Error: fee_level must be in { FEE_DEPTH_TARGETS } " )
self . FEE_EST_USE_MEMPOOL = True
self . FEE_EST_DYNAMIC = True
self . FEE_EST_DYNAMIC_MEMPOOL_SLIDERPOS = FEE_DEPTH_TARGETS . index ( value )
elif fee_method == ' ETA ' :
if value not in FEE_ETA_TARGETS :
raise Exception ( f " Error: fee_level must be in { FEE_ETA_TARGETS } " )
self . FEE_EST_USE_MEMPOOL = False
self . FEE_EST_DYNAMIC = True
self . FEE_EST_DYNAMIC_ETA_SLIDERPOS = FEE_ETA_TARGETS . index ( value )
elif fee_method == ' static ' :
self . FEE_EST_DYNAMIC = False
self . FEE_EST_STATIC_FEERATE = value
else :
raise Exception ( f " Invalid parameter: { fee_method } . Valid methods are: ETA, mempool, static. " )
2018-01-03 18:08:10 +01:00
def fee_per_byte ( self ) :
""" Returns sat/vB fee to pay for a txn.
Note : might return None .
"""
fee_per_kb = self . fee_per_kb ( )
return fee_per_kb / 1000 if fee_per_kb is not None else None
2019-09-22 20:46:01 +02:00
def estimate_fee ( self , size : Union [ int , float , Decimal ] , * ,
allow_fallback_to_static_rates : bool = False ) - > int :
2018-01-03 18:08:10 +01:00
fee_per_kb = self . fee_per_kb ( )
if fee_per_kb is None :
2019-09-22 20:46:01 +02:00
if allow_fallback_to_static_rates :
fee_per_kb = FEERATE_FALLBACK_STATIC_FEE
else :
raise NoDynamicFeeEstimates ( )
2018-01-03 18:08:10 +01:00
return self . estimate_fee_for_feerate ( fee_per_kb , size )
2017-12-18 22:26:29 +01:00
@classmethod
2019-06-20 22:40:57 +02:00
def estimate_fee_for_feerate ( cls , fee_per_kb : Union [ int , float , Decimal ] ,
size : Union [ int , float , Decimal ] ) - > int :
2023-11-22 17:50:29 +00:00
# note: 'size' is in vbytes
2019-06-20 22:40:57 +02:00
size = Decimal ( size )
2018-05-09 19:30:18 +02:00
fee_per_kb = Decimal ( fee_per_kb )
fee_per_byte = fee_per_kb / 1000
2018-05-14 17:49:17 +02:00
# to be consistent with what is displayed in the GUI,
# the calculation needs to use the same precision:
fee_per_byte = quantize_feerate ( fee_per_byte )
2018-05-09 19:30:18 +02:00
return round ( fee_per_byte * size )
2017-11-29 13:45:02 +01:00
2021-04-15 19:00:46 +02:00
def update_fee_estimates ( self , nblock_target : int , fee_per_kb : int ) :
assert isinstance ( nblock_target , int ) , f " expected int, got { nblock_target !r} "
assert isinstance ( fee_per_kb , int ) , f " expected int, got { fee_per_kb !r} "
self . fee_estimates [ nblock_target ] = fee_per_kb
2017-09-21 01:19:05 +02:00
def is_fee_estimates_update_required ( self ) :
""" Checks time since last requested and updated fee estimates.
Returns True if an update should be requested .
"""
now = time . time ( )
2018-03-01 18:31:16 +01:00
return now - self . last_time_fee_estimates_requested > 60
2017-09-21 01:19:05 +02:00
def requested_fee_estimates ( self ) :
self . last_time_fee_estimates_requested = time . time ( )
2017-02-17 20:56:38 +01:00
def get_video_device ( self ) :
2023-05-24 17:41:44 +00:00
device = self . VIDEO_DEVICE_PATH
2017-02-17 20:56:38 +01:00
if device == ' default ' :
device = ' '
return device
2023-03-31 22:17:53 +00:00
def format_amount (
self ,
amount_sat ,
* ,
is_diff = False ,
whitespaces = False ,
precision = None ,
2023-06-13 15:59:18 +00:00
add_thousands_sep : bool = None ,
2023-03-31 22:17:53 +00:00
) - > str :
if precision is None :
precision = self . amt_precision_post_satoshi
2023-06-13 15:59:18 +00:00
if add_thousands_sep is None :
add_thousands_sep = self . amt_add_thousands_sep
2020-06-22 02:46:16 +02:00
return format_satoshis (
2023-03-31 22:17:53 +00:00
amount_sat ,
2020-06-22 02:46:16 +02:00
num_zeros = self . num_zeros ,
decimal_point = self . decimal_point ,
is_diff = is_diff ,
whitespaces = whitespaces ,
2023-03-31 22:17:53 +00:00
precision = precision ,
2023-06-13 15:59:18 +00:00
add_thousands_sep = add_thousands_sep ,
2020-06-22 02:46:16 +02:00
)
2020-05-26 15:37:55 +02:00
2023-03-31 22:17:53 +00:00
def format_amount_and_units ( self , * args , * * kwargs ) - > str :
return self . format_amount ( * args , * * kwargs ) + ' ' + self . get_base_unit ( )
2020-05-26 15:37:55 +02:00
def format_fee_rate ( self , fee_rate ) :
return format_fee_satoshis ( fee_rate / 1000 , num_zeros = self . num_zeros ) + ' sat/byte '
def get_base_unit ( self ) :
return decimal_point_to_base_unit_name ( self . decimal_point )
def set_base_unit ( self , unit ) :
assert unit in base_units . keys ( )
self . decimal_point = base_unit_name_to_decimal_point ( unit )
2023-05-24 17:41:44 +00:00
self . BTC_AMOUNTS_DECIMAL_POINT = self . decimal_point
2020-05-26 15:37:55 +02:00
def get_decimal_point ( self ) :
return self . decimal_point
2023-05-24 17:41:44 +00:00
@cached_property
2023-05-30 14:03:03 +00:00
def cv ( config ) :
2023-05-24 17:41:44 +00:00
""" Allows getting a reference to a config variable without dereferencing it.
2017-02-17 20:56:38 +01:00
2023-05-24 17:41:44 +00:00
Compare :
>> > config . NETWORK_SERVER
' testnet.hsmiths.com:53012:s '
>> > config . cv . NETWORK_SERVER
< ConfigVarWithConfig key = ' server ' >
"""
class CVLookupHelper :
2023-05-30 14:03:03 +00:00
def __getattribute__ ( self , name : str ) - > ConfigVarWithConfig :
2023-09-08 14:55:22 +00:00
if name in ( " from_key " , ) : # don't apply magic, just use standard lookup
return super ( ) . __getattribute__ ( name )
2023-05-30 14:03:03 +00:00
config_var = config . __class__ . __getattribute__ ( type ( config ) , name )
2023-05-24 17:41:44 +00:00
if not isinstance ( config_var , ConfigVar ) :
raise AttributeError ( )
2023-05-30 14:03:03 +00:00
return ConfigVarWithConfig ( config = config , config_var = config_var )
2023-09-08 14:55:22 +00:00
def from_key ( self , key : str ) - > ConfigVarWithConfig :
try :
config_var = _config_var_from_key [ key ]
except KeyError :
raise KeyError ( f " No ConfigVar with key= { key !r} " ) from None
return ConfigVarWithConfig ( config = config , config_var = config_var )
2023-05-24 17:41:44 +00:00
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 ( )
2023-09-07 15:30:46 +00:00
def _default_swapserver_url ( self ) - > str :
2023-08-10 09:02:42 +02:00
if constants . net == constants . BitcoinMainnet :
default = ' https://swaps.electrum.org/api '
elif constants . net == constants . BitcoinTestnet :
default = ' https://swaps.electrum.org/testnet '
else :
2023-08-10 10:29:32 +02:00
default = ' http://localhost:5455 '
2023-09-07 15:30:46 +00:00
return default
2023-08-10 09:02:42 +02:00
2023-06-19 14:46:56 +02:00
# config variables ----->
2023-05-24 17:41:44 +00:00
NETWORK_AUTO_CONNECT = ConfigVar ( ' auto_connect ' , default = True , type_ = bool )
NETWORK_ONESERVER = ConfigVar ( ' oneserver ' , default = False , type_ = bool )
2023-11-30 13:58:15 +01:00
NETWORK_PROXY = ConfigVar ( ' proxy ' , default = None , type_ = str )
NETWORK_PROXY_USER = ConfigVar ( ' proxy_user ' , default = None , type_ = str )
NETWORK_PROXY_PASSWORD = ConfigVar ( ' proxy_password ' , default = None , type_ = str )
2023-05-24 17:41:44 +00:00
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 )
2023-09-18 11:00:52 +00:00
WALLET_BATCH_RBF = ConfigVar (
' batch_rbf ' , default = False , type_ = bool ,
short_desc = lambda : _ ( ' Batch unconfirmed transactions ' ) ,
long_desc = lambda : (
_ ( ' 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 ' ) ) ,
)
2023-10-23 19:06:42 +00:00
WALLET_MERGE_DUPLICATE_OUTPUTS = ConfigVar (
' wallet_merge_duplicate_outputs ' , default = False , type_ = bool ,
short_desc = lambda : _ ( ' Merge duplicate outputs ' ) ,
long_desc = lambda : _ ( ' Merge transaction outputs that pay to the same address into '
' a single output that pays the sum of the original amounts. ' ) ,
)
2023-09-18 11:00:52 +00:00
WALLET_SPEND_CONFIRMED_ONLY = ConfigVar (
' confirmed_only ' , default = False , type_ = bool ,
short_desc = lambda : _ ( ' Spend only confirmed coins ' ) ,
long_desc = lambda : _ ( ' Spend only confirmed inputs. ' ) ,
)
2023-05-24 17:41:44 +00:00
WALLET_COIN_CHOOSER_POLICY = ConfigVar ( ' coin_chooser ' , default = ' Privacy ' , type_ = str )
2023-09-18 11:00:52 +00:00
WALLET_COIN_CHOOSER_OUTPUT_ROUNDING = ConfigVar (
' coin_chooser_output_rounding ' , default = True , type_ = bool ,
short_desc = lambda : _ ( ' Enable output value rounding ' ) ,
long_desc = lambda : (
_ ( ' Set the value of the change output so that it has similar precision to the other outputs. ' ) + ' \n ' +
_ ( ' This might improve your privacy somewhat. ' ) + ' \n ' +
_ ( ' If enabled, at most 100 satoshis might be lost due to this, per transaction. ' ) ) ,
)
2023-05-24 17:41:44 +00:00
WALLET_UNCONF_UTXO_FREEZE_THRESHOLD_SAT = ConfigVar ( ' unconf_utxo_freeze_threshold ' , default = 5_000 , type_ = int )
2023-09-18 11:00:52 +00:00
WALLET_BIP21_LIGHTNING = ConfigVar (
' bip21_lightning ' , default = False , type_ = bool ,
short_desc = lambda : _ ( ' Add lightning requests to bitcoin URIs ' ) ,
long_desc = lambda : _ ( ' This may result in large QR codes ' ) ,
)
WALLET_BOLT11_FALLBACK = ConfigVar (
' bolt11_fallback ' , default = True , type_ = bool ,
short_desc = lambda : _ ( ' Add on-chain fallback to lightning requests ' ) ,
)
2023-05-24 17:41:44 +00:00
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
2023-09-18 11:00:52 +00:00
WALLET_SEND_CHANGE_TO_LIGHTNING = ConfigVar (
' send_change_to_lightning ' , default = False , type_ = bool ,
short_desc = lambda : _ ( ' Send change to Lightning ' ) ,
long_desc = lambda : _ ( ' If possible, send the change of this transaction to your channels, with a submarine swap ' ) ,
)
2023-05-24 17:41:44 +00:00
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
2023-09-18 11:00:52 +00:00
FX_HISTORY_RATES = ConfigVar (
' history_rates ' , default = False , type_ = bool ,
short_desc = lambda : _ ( ' Download historical rates ' ) ,
)
FX_HISTORY_RATES_CAPITAL_GAINS = ConfigVar (
' history_rates_capital_gains ' , default = False , type_ = bool ,
short_desc = lambda : _ ( ' Show Capital Gains ' ) ,
)
FX_SHOW_FIAT_BALANCE_FOR_ADDRESSES = ConfigVar (
' fiat_address ' , default = False , type_ = bool ,
short_desc = lambda : _ ( ' Show Fiat balances ' ) ,
)
2023-05-24 17:41:44 +00:00
LIGHTNING_LISTEN = ConfigVar ( ' lightning_listen ' , default = None , type_ = str )
LIGHTNING_PEERS = ConfigVar ( ' lightning_peers ' , default = None )
2023-09-18 11:00:52 +00:00
LIGHTNING_USE_GOSSIP = ConfigVar (
' use_gossip ' , default = False , type_ = bool ,
short_desc = lambda : _ ( " Use trampoline routing " ) ,
long_desc = lambda : _ ( """ Lightning payments require finding a path through the Lightning Network. You may use trampoline routing, or local routing (gossip).
Downloading the network gossip uses quite some bandwidth and storage , and is not recommended on mobile devices . If you use trampoline , you can only open channels with trampoline nodes . """ ),
)
LIGHTNING_USE_RECOVERABLE_CHANNELS = ConfigVar (
' use_recoverable_channels ' , default = True , type_ = bool ,
short_desc = lambda : _ ( " Create recoverable channels " ) ,
long_desc = lambda : _ ( """ Add extra data to your channel funding transactions, so that a static backup can be recovered from your seed.
Note that static backups only allow you to request a force - close with the remote node . This assumes that the remote node is still online , did not lose its data , and accepts to force close the channel .
If this is enabled , other nodes cannot open a channel to you . Channel recovery data is encrypted , so that only your wallet can decrypt it . However , blockchain analysis will be able to tell that the transaction was probably created by Electrum . """ ),
)
LIGHTNING_ALLOW_INSTANT_SWAPS = ConfigVar (
' allow_instant_swaps ' , default = False , type_ = bool ,
short_desc = lambda : _ ( " Allow instant swaps " ) ,
long_desc = lambda : _ ( """ If this option is checked, your client will complete reverse swaps before the funding transaction is confirmed.
Note you are at risk of losing the funds in the swap , if the funding transaction never confirms . """ ),
)
2023-05-24 17:41:44 +00:00
LIGHTNING_TO_SELF_DELAY_CSV = ConfigVar ( ' lightning_to_self_delay ' , default = 7 * 144 , type_ = int )
2023-08-09 15:38:59 +00:00
LIGHTNING_MAX_FUNDING_SAT = ConfigVar ( ' lightning_max_funding_sat ' , default = LN_MAX_FUNDING_SAT_LEGACY , type_ = int )
2023-09-18 11:00:52 +00:00
LIGHTNING_LEGACY_ADD_TRAMPOLINE = ConfigVar (
' lightning_legacy_add_trampoline ' , default = False , type_ = bool ,
short_desc = lambda : _ ( " Add extra trampoline to legacy payments " ) ,
long_desc = lambda : _ ( """ When paying a non-trampoline invoice, add an extra trampoline to the route, in order to improve your privacy.
This will result in longer routes ; it might increase your fees and decrease the success rate of your payments . """ ),
)
2023-10-08 15:06:19 +02:00
INITIAL_TRAMPOLINE_FEE_LEVEL = ConfigVar ( ' initial_trampoline_fee_level ' , default = 1 , type_ = int )
2023-05-24 17:41:44 +00:00
2023-10-13 15:49:13 +02:00
LIGHTNING_NODE_ALIAS = ConfigVar ( ' lightning_node_alias ' , default = ' ' , type_ = str )
2023-05-24 17:41:44 +00:00
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 )
2023-06-20 14:42:24 +02:00
TEST_FORCE_MPP = ConfigVar ( ' test_force_mpp ' , default = False , type_ = bool )
2023-08-11 15:08:18 +00:00
TEST_FORCE_DISABLE_MPP = ConfigVar ( ' test_force_disable_mpp ' , default = False , type_ = bool )
2023-05-24 17:41:44 +00:00
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 )
2023-10-08 17:43:42 +02:00
FEE_EST_STATIC_FEERATE = ConfigVar ( ' fee_per_kb ' , default = FEERATE_FALLBACK_STATIC_FEE , type_ = int )
2023-05-24 17:41:44 +00:00
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 )
2023-09-18 11:00:52 +00:00
GUI_QT_COLOR_THEME = ConfigVar (
' qt_gui_color_theme ' , default = ' default ' , type_ = str ,
short_desc = lambda : _ ( ' Color theme ' ) ,
)
2023-05-24 17:41:44 +00:00
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 )
2023-09-18 11:00:52 +00:00
GUI_QT_TX_DIALOG_FETCH_TXIN_DATA = ConfigVar (
' tx_dialog_fetch_txin_data ' , default = False , type_ = bool ,
short_desc = lambda : _ ( ' Download missing data ' ) ,
long_desc = lambda : _ (
' Download parent transactions from the network. \n '
' Allows filling in missing fee and input details. ' ) ,
)
2023-05-24 17:41:44 +00:00
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 )
2023-09-18 11:00:52 +00:00
GUI_QT_TX_EDITOR_SHOW_IO = ConfigVar (
' show_tx_io ' , default = False , type_ = bool ,
short_desc = lambda : _ ( ' Show inputs and outputs ' ) ,
)
GUI_QT_TX_EDITOR_SHOW_FEE_DETAILS = ConfigVar (
' show_tx_fee_details ' , default = False , type_ = bool ,
short_desc = lambda : _ ( ' Edit fees manually ' ) ,
)
GUI_QT_TX_EDITOR_SHOW_LOCKTIME = ConfigVar (
' show_tx_locktime ' , default = False , type_ = bool ,
short_desc = lambda : _ ( ' Edit Locktime ' ) ,
)
2023-05-30 13:47:56 +00:00
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 )
2023-05-24 17:41:44 +00:00
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 )
2023-11-01 18:23:16 +01:00
GUI_QML_ADDRESS_LIST_SHOW_TYPE = ConfigVar ( ' address_list_show_type ' , default = 1 , type_ = int )
GUI_QML_ADDRESS_LIST_SHOW_USED = ConfigVar ( ' address_list_show_used ' , default = False , type_ = bool )
2023-05-24 17:41:44 +00:00
BTC_AMOUNTS_DECIMAL_POINT = ConfigVar ( ' decimal_point ' , default = DECIMAL_POINT_DEFAULT , type_ = int )
2023-09-18 11:00:52 +00:00
BTC_AMOUNTS_FORCE_NZEROS_AFTER_DECIMAL_POINT = ConfigVar (
' num_zeros ' , default = 0 , type_ = int ,
short_desc = lambda : _ ( ' Zeros after decimal point ' ) ,
long_desc = lambda : _ ( ' Number of zeros displayed after the decimal point. For example, if this is set to 2, " 1. " will be displayed as " 1.00 " ' ) ,
)
BTC_AMOUNTS_PREC_POST_SAT = ConfigVar (
' amt_precision_post_satoshi ' , default = 0 , type_ = int ,
short_desc = lambda : _ ( " Show Lightning amounts with msat precision " ) ,
)
BTC_AMOUNTS_ADD_THOUSANDS_SEP = ConfigVar (
' amt_add_thousands_sep ' , default = False , type_ = bool ,
short_desc = lambda : _ ( " Add thousand separators to bitcoin amounts " ) ,
)
BLOCK_EXPLORER = ConfigVar (
' block_explorer ' , default = ' Blockstream.info ' , type_ = str ,
short_desc = lambda : _ ( ' Online Block Explorer ' ) ,
long_desc = lambda : _ ( ' Choose which online block explorer to use for functions that open a web browser ' ) ,
)
2023-05-24 17:41:44 +00:00
BLOCK_EXPLORER_CUSTOM = ConfigVar ( ' block_explorer_custom ' , default = None )
2023-09-18 11:00:52 +00:00
VIDEO_DEVICE_PATH = ConfigVar (
' video_device ' , default = ' default ' , type_ = str ,
short_desc = lambda : _ ( ' Video Device ' ) ,
long_desc = lambda : ( _ ( " For scanning QR codes. " ) + " \n " +
_ ( " Install the zbar package to enable this. " ) ) ,
)
OPENALIAS_ID = ConfigVar (
' alias ' , default = " " , type_ = str ,
short_desc = lambda : ' OpenAlias ' ,
long_desc = lambda : (
_ ( ' OpenAlias record, used to receive coins and to sign payment requests. ' ) + ' \n \n ' +
_ ( ' The following alias providers are available: ' ) + ' \n ' +
' \n ' . join ( [ ' https://cryptoname.co/ ' , ' http://xmr.link ' ] ) + ' \n \n ' +
' For more information, see https://openalias.org ' ) ,
)
2023-05-24 17:41:44 +00:00
HWD_SESSION_TIMEOUT = ConfigVar ( ' session_timeout ' , default = 300 , type_ = int )
CLI_TIMEOUT = ConfigVar ( ' timeout ' , default = 60 , type_ = float )
2023-09-18 11:00:52 +00:00
AUTOMATIC_CENTRALIZED_UPDATE_CHECKS = ConfigVar (
' check_updates ' , default = False , type_ = bool ,
short_desc = lambda : _ ( " Automatically check for software updates " ) ,
)
WRITE_LOGS_TO_DISK = ConfigVar (
' log_to_file ' , default = False , type_ = bool ,
short_desc = lambda : _ ( " Write logs to file " ) ,
long_desc = lambda : _ ( ' Debug logs can be persisted to disk. These are useful for troubleshooting. ' ) ,
)
2023-09-06 16:55:04 +00:00
LOGS_NUM_FILES_KEEP = ConfigVar ( ' logs_num_files_keep ' , default = 10 , type_ = int )
2023-05-24 17:41:44 +00:00
GUI_ENABLE_DEBUG_LOGS = ConfigVar ( ' gui_enable_debug_logs ' , default = False , type_ = bool )
2023-09-18 11:00:52 +00:00
LOCALIZATION_LANGUAGE = ConfigVar (
' language ' , default = " " , type_ = str ,
short_desc = lambda : _ ( " Language " ) ,
long_desc = lambda : _ ( " Select which language is used in the GUI (after restart). " ) ,
)
2023-05-24 17:41:44 +00:00
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 )
2023-06-19 14:46:56 +02:00
# submarine swap server
2023-09-07 15:30:46 +00:00
SWAPSERVER_URL = ConfigVar ( ' swapserver_url ' , default = _default_swapserver_url , type_ = str )
2023-08-11 08:12:54 +02:00
SWAPSERVER_PORT = ConfigVar ( ' swapserver_port ' , default = 5455 , type_ = int )
2023-06-28 14:23:32 +02:00
TEST_SWAPSERVER_REFUND = ConfigVar ( ' test_swapserver_refund ' , default = False , type_ = bool )
2023-08-11 08:12:54 +02:00
2023-08-08 05:09:58 +02:00
# zeroconf channels
2023-08-08 05:09:58 +02:00
ACCEPT_ZEROCONF_CHANNELS = ConfigVar ( ' accept_zeroconf_channels ' , default = False , type_ = bool )
ZEROCONF_TRUSTED_NODE = ConfigVar ( ' zeroconf_trusted_node ' , default = ' ' , type_ = str )
2023-08-08 05:09:58 +02:00
ZEROCONF_MIN_OPENING_FEE = ConfigVar ( ' zeroconf_min_opening_fee ' , default = 5000 , type_ = int )
2023-08-08 05:09:58 +02:00
2023-05-24 17:41:44 +00:00
# connect to remote WT
2023-09-18 11:00:52 +00:00
WATCHTOWER_CLIENT_ENABLED = ConfigVar (
' use_watchtower ' , default = False , type_ = bool ,
short_desc = lambda : _ ( " Use a remote watchtower " ) ,
long_desc = lambda : ' ' . join ( [
_ ( " A watchtower is a daemon that watches your channels and prevents the other party from stealing funds by broadcasting an old state. " ) ,
_ ( " If you have private a watchtower, enter its URL here. " ) ,
_ ( " Check our online documentation if you want to configure Electrum as a watchtower. " ) ,
] ) ,
)
2023-05-24 17:41:44 +00:00
WATCHTOWER_CLIENT_URL = ConfigVar ( ' watchtower_url ' , default = None , type_ = str )
# run WT locally
WATCHTOWER_SERVER_ENABLED = ConfigVar ( ' run_watchtower ' , default = False , type_ = bool )
2023-08-11 08:12:54 +02:00
WATCHTOWER_SERVER_PORT = ConfigVar ( ' watchtower_port ' , default = None , type_ = int )
2023-05-24 17:41:44 +00:00
WATCHTOWER_SERVER_USER = ConfigVar ( ' watchtower_user ' , default = None , type_ = str )
WATCHTOWER_SERVER_PASSWORD = ConfigVar ( ' watchtower_password ' , default = None , type_ = str )
2023-08-11 08:12:54 +02:00
PAYSERVER_PORT = ConfigVar ( ' payserver_port ' , default = 8080 , type_ = int )
2023-05-24 17:41:44 +00:00
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 ] :
2014-06-25 17:34:51 +02:00
""" Parse and store the user config settings in electrum.conf into user_config[]. """
2015-04-02 10:00:07 +02:00
if not path :
return { }
2014-06-25 17:34:51 +02:00
config_path = os . path . join ( path , " config " )
2016-04-12 19:56:47 +02:00
if not os . path . exists ( config_path ) :
return { }
2015-04-02 10:00:07 +02:00
try :
2018-03-23 21:47:51 +01:00
with open ( config_path , " r " , encoding = ' utf-8 ' ) as f :
2015-04-02 10:00:07 +02:00
data = f . read ( )
result = json . loads ( data )
2022-10-24 15:45:44 +00:00
except Exception as exc :
_logger . warning ( f " Cannot read config file at { config_path } : { exc } " )
2016-04-12 19:56:47 +02:00
return { }
2015-04-02 10:00:07 +02:00
if not type ( result ) is dict :
return { }
2014-06-26 11:08:13 +02:00
return result