Browse Source

Merge pull request #5296 from SomberNight/logging_20190328

use stdlib logging module instead of print_error
regtest_lnd
ThomasV 6 years ago
committed by GitHub
parent
commit
a3e522efd9
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      electrum/__init__.py
  2. 10
      electrum/address_synchronizer.py
  3. 16
      electrum/base_wizard.py
  4. 6
      electrum/bip32.py
  5. 16
      electrum/blockchain.py
  6. 20
      electrum/coinchooser.py
  7. 7
      electrum/commands.py
  8. 8
      electrum/contacts.py
  9. 19
      electrum/daemon.py
  10. 8
      electrum/dnssec.py
  11. 6
      electrum/ecc.py
  12. 19
      electrum/ecc_fast.py
  13. 24
      electrum/exchange_rate.py
  14. 7
      electrum/gui/kivy/uix/dialogs/crash_reporter.py
  15. 18
      electrum/gui/qt/__init__.py
  16. 18
      electrum/gui/qt/exception_window.py
  17. 18
      electrum/gui/qt/history_list.py
  18. 10
      electrum/gui/qt/installwizard.py
  19. 42
      electrum/gui/qt/main_window.py
  20. 7
      electrum/gui/qt/network_dialog.py
  21. 8
      electrum/gui/qt/paytoedit.py
  22. 4
      electrum/gui/qt/transaction_dialog.py
  23. 15
      electrum/gui/qt/update_checker.py
  24. 6
      electrum/gui/stdio.py
  25. 6
      electrum/gui/text.py
  26. 51
      electrum/interface.py
  27. 16
      electrum/json_db.py
  28. 8
      electrum/jsonrpc.py
  29. 15
      electrum/keystore.py
  30. 164
      electrum/logging.py
  31. 15
      electrum/mnemonic.py
  32. 65
      electrum/network.py
  33. 15
      electrum/paymentrequest.py
  34. 51
      electrum/plugin.py
  35. 14
      electrum/plugins/audio_modem/qt.py
  36. 12
      electrum/plugins/coldcard/cmdline.py
  37. 33
      electrum/plugins/coldcard/coldcard.py
  38. 16
      electrum/plugins/cosigner_pool/qt.py
  39. 15
      electrum/plugins/digitalbitbox/digitalbitbox.py
  40. 22
      electrum/plugins/email_requests/qt.py
  41. 3
      electrum/plugins/greenaddress_instant/qt.py
  42. 8
      electrum/plugins/hw_wallet/cmdline.py
  43. 2
      electrum/plugins/hw_wallet/plugin.py
  44. 8
      electrum/plugins/hw_wallet/qt.py
  45. 14
      electrum/plugins/keepkey/clientbase.py
  46. 18
      electrum/plugins/keepkey/keepkey.py
  47. 2
      electrum/plugins/labels/cmdline.py
  48. 2
      electrum/plugins/labels/kivy.py
  49. 16
      electrum/plugins/labels/labels.py
  50. 6
      electrum/plugins/labels/qt.py
  51. 10
      electrum/plugins/ledger/auth2fa.py
  52. 17
      electrum/plugins/ledger/ledger.py
  53. 2
      electrum/plugins/revealer/qt.py
  54. 14
      electrum/plugins/safe_t/clientbase.py
  55. 14
      electrum/plugins/safe_t/safe_t.py
  56. 10
      electrum/plugins/safe_t/transport.py
  57. 14
      electrum/plugins/trezor/clientbase.py
  58. 17
      electrum/plugins/trezor/trezor.py
  59. 4
      electrum/plugins/trustedcoin/cmdline.py
  60. 14
      electrum/plugins/trustedcoin/qt.py
  61. 25
      electrum/plugins/trustedcoin/trustedcoin.py
  62. 29
      electrum/simple_config.py
  63. 12
      electrum/storage.py
  64. 17
      electrum/synchronizer.py
  65. 35
      electrum/transaction.py
  66. 82
      electrum/util.py
  67. 18
      electrum/verifier.py
  68. 20
      electrum/wallet.py
  69. 18
      electrum/websockets.py
  70. 7
      electrum/x509.py
  71. 22
      run_electrum

2
electrum/__init__.py

@ -1,5 +1,5 @@
from .version import ELECTRUM_VERSION from .version import ELECTRUM_VERSION
from .util import format_satoshis, print_msg, print_error, set_verbosity from .util import format_satoshis
from .wallet import Wallet from .wallet import Wallet
from .storage import WalletStorage from .storage import WalletStorage
from .coinchooser import COIN_CHOOSERS from .coinchooser import COIN_CHOOSERS

10
electrum/address_synchronizer.py

@ -29,12 +29,13 @@ from typing import TYPE_CHECKING, Dict, Optional, Set, Tuple
from . import bitcoin from . import bitcoin
from .bitcoin import COINBASE_MATURITY, TYPE_ADDRESS, TYPE_PUBKEY from .bitcoin import COINBASE_MATURITY, TYPE_ADDRESS, TYPE_PUBKEY
from .util import PrintError, profiler, bfh, TxMinedInfo from .util import profiler, bfh, TxMinedInfo
from .transaction import Transaction, TxOutput from .transaction import Transaction, TxOutput
from .synchronizer import Synchronizer from .synchronizer import Synchronizer
from .verifier import SPV from .verifier import SPV
from .blockchain import hash_header from .blockchain import hash_header
from .i18n import _ from .i18n import _
from .logging import Logger
if TYPE_CHECKING: if TYPE_CHECKING:
from .storage import WalletStorage from .storage import WalletStorage
@ -54,7 +55,7 @@ class UnrelatedTransactionException(AddTransactionException):
return _("Transaction is unrelated to this wallet.") return _("Transaction is unrelated to this wallet.")
class AddressSynchronizer(PrintError): class AddressSynchronizer(Logger):
""" """
inherited by wallet inherited by wallet
""" """
@ -63,6 +64,7 @@ class AddressSynchronizer(PrintError):
self.storage = storage self.storage = storage
self.db = self.storage.db self.db = self.storage.db
self.network = None # type: Network self.network = None # type: Network
Logger.__init__(self)
# verifier (SPV) and synchronizer are started in start_network # verifier (SPV) and synchronizer are started in start_network
self.synchronizer = None # type: Synchronizer self.synchronizer = None # type: Synchronizer
self.verifier = None # type: SPV self.verifier = None # type: SPV
@ -307,7 +309,7 @@ class AddressSynchronizer(PrintError):
self.db.remove_spent_outpoint(prevout_hash, prevout_n) self.db.remove_spent_outpoint(prevout_hash, prevout_n)
with self.transaction_lock: with self.transaction_lock:
self.print_error("removing tx from history", tx_hash) self.logger.info("removing tx from history", tx_hash)
tx = self.db.remove_transaction(tx_hash) tx = self.db.remove_transaction(tx_hash)
remove_from_spent_outpoints() remove_from_spent_outpoints()
self._remove_tx_from_local_history(tx_hash) self._remove_tx_from_local_history(tx_hash)
@ -455,7 +457,7 @@ class AddressSynchronizer(PrintError):
h2.reverse() h2.reverse()
# fixme: this may happen if history is incomplete # fixme: this may happen if history is incomplete
if balance not in [None, 0]: if balance not in [None, 0]:
self.print_error("Error: history not synchronized") self.logger.info("Error: history not synchronized")
return [] return []
return h2 return h2

16
electrum/base_wizard.py

@ -43,6 +43,7 @@ from .i18n import _
from .util import UserCancelled, InvalidPassword, WalletFileException from .util import UserCancelled, InvalidPassword, WalletFileException
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
from .plugin import Plugins from .plugin import Plugins
from .logging import Logger
if TYPE_CHECKING: if TYPE_CHECKING:
from .plugin import DeviceInfo from .plugin import DeviceInfo
@ -65,10 +66,11 @@ class WizardStackItem(NamedTuple):
storage_data: dict storage_data: dict
class BaseWizard(object): class BaseWizard(Logger):
def __init__(self, config: SimpleConfig, plugins: Plugins): def __init__(self, config: SimpleConfig, plugins: Plugins):
super(BaseWizard, self).__init__() super(BaseWizard, self).__init__()
Logger.__init__(self)
self.config = config self.config = config
self.plugins = plugins self.plugins = plugins
self.data = {} self.data = {}
@ -253,7 +255,7 @@ class BaseWizard(object):
def failed_getting_device_infos(name, e): def failed_getting_device_infos(name, e):
nonlocal debug_msg nonlocal debug_msg
devmgr.print_error(f'error getting device infos for {name}: {e}') self.logger.info(f'error getting device infos for {name}: {e}')
indented_error_msg = ' '.join([''] + str(e).splitlines(keepends=True)) indented_error_msg = ' '.join([''] + str(e).splitlines(keepends=True))
debug_msg += f' {name}: (error getting device infos)\n{indented_error_msg}\n' debug_msg += f' {name}: (error getting device infos)\n{indented_error_msg}\n'
@ -261,7 +263,7 @@ class BaseWizard(object):
try: try:
scanned_devices = devmgr.scan_devices() scanned_devices = devmgr.scan_devices()
except BaseException as e: except BaseException as e:
devmgr.print_error('error scanning devices: {}'.format(repr(e))) self.logger.info('error scanning devices: {}'.format(repr(e)))
debug_msg = ' {}:\n {}'.format(_('Error scanning devices'), e) debug_msg = ' {}:\n {}'.format(_('Error scanning devices'), e)
else: else:
for splugin in supported_plugins: for splugin in supported_plugins:
@ -280,7 +282,7 @@ class BaseWizard(object):
device_infos = devmgr.unpaired_device_infos(None, plugin, devices=scanned_devices, device_infos = devmgr.unpaired_device_infos(None, plugin, devices=scanned_devices,
include_failing_clients=True) include_failing_clients=True)
except BaseException as e: except BaseException as e:
traceback.print_exc() self.logger.exception('')
failed_getting_device_infos(name, e) failed_getting_device_infos(name, e)
continue continue
device_infos_failing = list(filter(lambda di: di.exception is not None, device_infos)) device_infos_failing = list(filter(lambda di: di.exception is not None, device_infos))
@ -333,7 +335,7 @@ class BaseWizard(object):
self.choose_hw_device(purpose, storage=storage) self.choose_hw_device(purpose, storage=storage)
return return
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
self.show_error(str(e)) self.show_error(str(e))
self.choose_hw_device(purpose, storage=storage) self.choose_hw_device(purpose, storage=storage)
return return
@ -399,7 +401,7 @@ class BaseWizard(object):
except ScriptTypeNotSupported: except ScriptTypeNotSupported:
raise # this is handled in derivation_dialog raise # this is handled in derivation_dialog
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
self.show_error(e) self.show_error(e)
return return
d = { d = {
@ -517,7 +519,7 @@ class BaseWizard(object):
self.choose_hw_device() self.choose_hw_device()
return return
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
self.show_error(str(e)) self.show_error(str(e))
return return
self.request_storage_encryption( self.request_storage_encryption(

6
electrum/bip32.py

@ -5,13 +5,15 @@
import hashlib import hashlib
from typing import List, Tuple, NamedTuple, Union, Iterable from typing import List, Tuple, NamedTuple, Union, Iterable
from .util import bfh, bh2u, BitcoinException, print_error from .util import bfh, bh2u, BitcoinException
from . import constants from . import constants
from . import ecc from . import ecc
from .crypto import hash_160, hmac_oneshot from .crypto import hash_160, hmac_oneshot
from .bitcoin import rev_hex, int_to_hex, EncodeBase58Check, DecodeBase58Check from .bitcoin import rev_hex, int_to_hex, EncodeBase58Check, DecodeBase58Check
from .logging import get_logger
_logger = get_logger(__name__)
BIP32_PRIME = 0x80000000 BIP32_PRIME = 0x80000000
UINT32_MAX = (1 << 32) - 1 UINT32_MAX = (1 << 32) - 1
@ -24,7 +26,7 @@ def protect_against_invalid_ecpoint(func):
try: try:
return func(*args[:-1], child_index=child_index) return func(*args[:-1], child_index=child_index)
except ecc.InvalidECPointException: except ecc.InvalidECPointException:
print_error('bip32 protect_against_invalid_ecpoint: skipping index') _logger.warning('bip32 protect_against_invalid_ecpoint: skipping index')
child_index += 1 child_index += 1
is_prime2 = child_index & BIP32_PRIME is_prime2 = child_index & BIP32_PRIME
if is_prime != is_prime2: raise OverflowError() if is_prime != is_prime2: raise OverflowError()

16
electrum/blockchain.py

@ -30,8 +30,11 @@ from .crypto import sha256d
from . import constants from . import constants
from .util import bfh, bh2u from .util import bfh, bh2u
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
from .logging import get_logger, Logger
_logger = get_logger(__name__)
HEADER_SIZE = 80 # bytes HEADER_SIZE = 80 # bytes
MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000 MAX_TARGET = 0x00000000FFFF0000000000000000000000000000000000000000000000000000
@ -96,7 +99,7 @@ def read_blockchains(config: 'SimpleConfig'):
if best_chain.height() > constants.net.max_checkpoint(): if best_chain.height() > constants.net.max_checkpoint():
header_after_cp = best_chain.read_header(constants.net.max_checkpoint()+1) header_after_cp = best_chain.read_header(constants.net.max_checkpoint()+1)
if not header_after_cp or not best_chain.can_connect(header_after_cp, check_height=False): if not header_after_cp or not best_chain.can_connect(header_after_cp, check_height=False):
util.print_error("[blockchain] deleting best chain. cannot connect header after last cp to last cp.") _logger.info("[blockchain] deleting best chain. cannot connect header after last cp to last cp.")
os.unlink(best_chain.path()) os.unlink(best_chain.path())
best_chain.update_size() best_chain.update_size()
# forks # forks
@ -107,7 +110,7 @@ def read_blockchains(config: 'SimpleConfig'):
l = sorted(l, key=lambda x: int(x.split('_')[1])) # sort by forkpoint l = sorted(l, key=lambda x: int(x.split('_')[1])) # sort by forkpoint
def delete_chain(filename, reason): def delete_chain(filename, reason):
util.print_error(f"[blockchain] deleting chain {filename}: {reason}") _logger.info(f"[blockchain] deleting chain {filename}: {reason}")
os.unlink(os.path.join(fdir, filename)) os.unlink(os.path.join(fdir, filename))
def instantiate_chain(filename): def instantiate_chain(filename):
@ -156,7 +159,7 @@ _CHAINWORK_CACHE = {
} # type: Dict[str, int] } # type: Dict[str, int]
class Blockchain(util.PrintError): class Blockchain(Logger):
""" """
Manages blockchain headers and their verification Manages blockchain headers and their verification
""" """
@ -168,6 +171,7 @@ class Blockchain(util.PrintError):
# assert (parent is None) == (forkpoint == 0) # assert (parent is None) == (forkpoint == 0)
if 0 < forkpoint <= constants.net.max_checkpoint(): if 0 < forkpoint <= constants.net.max_checkpoint():
raise Exception(f"cannot fork below max checkpoint. forkpoint: {forkpoint}") raise Exception(f"cannot fork below max checkpoint. forkpoint: {forkpoint}")
Logger.__init__(self)
self.config = config self.config = config
self.forkpoint = forkpoint # height of first header self.forkpoint = forkpoint # height of first header
self.parent = parent self.parent = parent
@ -368,7 +372,7 @@ class Blockchain(util.PrintError):
return False return False
if self.parent.get_chainwork() >= self.get_chainwork(): if self.parent.get_chainwork() >= self.get_chainwork():
return False return False
self.print_error("swap", self.forkpoint, self.parent.forkpoint) self.logger.info(f"swapping {self.forkpoint} {self.parent.forkpoint}")
parent_branch_size = self.parent.height() - self.forkpoint + 1 parent_branch_size = self.parent.height() - self.forkpoint + 1
forkpoint = self.forkpoint # type: Optional[int] forkpoint = self.forkpoint # type: Optional[int]
parent = self.parent # type: Optional[Blockchain] parent = self.parent # type: Optional[Blockchain]
@ -570,7 +574,6 @@ class Blockchain(util.PrintError):
return False return False
height = header['block_height'] height = header['block_height']
if check_height and self.height() != height - 1: if check_height and self.height() != height - 1:
#self.print_error("cannot connect at height", height)
return False return False
if height == 0: if height == 0:
return hash_header(header) == constants.net.GENESIS return hash_header(header) == constants.net.GENESIS
@ -595,11 +598,10 @@ class Blockchain(util.PrintError):
try: try:
data = bfh(hexdata) data = bfh(hexdata)
self.verify_chunk(idx, data) self.verify_chunk(idx, data)
#self.print_error("validated chunk %d" % idx)
self.save_chunk(idx, data) self.save_chunk(idx, data)
return True return True
except BaseException as e: except BaseException as e:
self.print_error(f'verify_chunk idx {idx} failed: {repr(e)}') self.logger.info(f'verify_chunk idx {idx} failed: {repr(e)}')
return False return False
def get_checkpoints(self): def get_checkpoints(self):

20
electrum/coinchooser.py

@ -28,7 +28,8 @@ from typing import NamedTuple, List
from .bitcoin import sha256, COIN, TYPE_ADDRESS, is_address from .bitcoin import sha256, COIN, TYPE_ADDRESS, is_address
from .transaction import Transaction, TxOutput from .transaction import Transaction, TxOutput
from .util import NotEnoughFunds, PrintError from .util import NotEnoughFunds
from .logging import Logger
# A simple deterministic PRNG. Used to deterministically shuffle a # A simple deterministic PRNG. Used to deterministically shuffle a
@ -92,10 +93,13 @@ def strip_unneeded(bkts, sufficient_funds):
raise Exception("keeping all buckets is still not enough") raise Exception("keeping all buckets is still not enough")
class CoinChooserBase(PrintError): class CoinChooserBase(Logger):
enable_output_value_rounding = False enable_output_value_rounding = False
def __init__(self):
Logger.__init__(self)
def keys(self, coins): def keys(self, coins):
raise NotImplementedError raise NotImplementedError
@ -187,9 +191,9 @@ class CoinChooserBase(PrintError):
amounts = [amount for amount in amounts if amount >= dust_threshold] amounts = [amount for amount in amounts if amount >= dust_threshold]
change = [TxOutput(TYPE_ADDRESS, addr, amount) change = [TxOutput(TYPE_ADDRESS, addr, amount)
for addr, amount in zip(change_addrs, amounts)] for addr, amount in zip(change_addrs, amounts)]
self.print_error('change:', change) self.logger.info(f'change: {change}')
if dust: if dust:
self.print_error('not keeping dust', dust) self.logger.info(f'not keeping dust {dust}')
return change return change
def make_tx(self, coins, inputs, outputs, change_addrs, fee_estimator, def make_tx(self, coins, inputs, outputs, change_addrs, fee_estimator,
@ -268,8 +272,8 @@ class CoinChooserBase(PrintError):
change = self.change_outputs(tx, change_addrs, fee, dust_threshold) change = self.change_outputs(tx, change_addrs, fee, dust_threshold)
tx.add_outputs(change) tx.add_outputs(change)
self.print_error("using %d inputs" % len(tx.inputs())) self.logger.info(f"using {len(tx.inputs())} inputs")
self.print_error("using buckets:", [bucket.desc for bucket in buckets]) self.logger.info(f"using buckets: {[bucket.desc for bucket in buckets]}")
return tx return tx
@ -357,8 +361,8 @@ class CoinChooserRandom(CoinChooserBase):
candidates = self.bucket_candidates_prefer_confirmed(buckets, sufficient_funds) candidates = self.bucket_candidates_prefer_confirmed(buckets, sufficient_funds)
penalties = [penalty_func(cand) for cand in candidates] penalties = [penalty_func(cand) for cand in candidates]
winner = candidates[penalties.index(min(penalties))] winner = candidates[penalties.index(min(penalties))]
self.print_error("Bucket sets:", len(buckets)) self.logger.info(f"Bucket sets: {len(buckets)}")
self.print_error("Winning penalty:", min(penalties)) self.logger.info(f"Winning penalty: {min(penalties)}")
return winner return winner
class CoinChooserPrivacy(CoinChooserRandom): class CoinChooserPrivacy(CoinChooserRandom):

7
electrum/commands.py

@ -35,7 +35,7 @@ from decimal import Decimal
from typing import Optional, TYPE_CHECKING from typing import Optional, TYPE_CHECKING
from .import util, ecc from .import util, ecc
from .util import bfh, bh2u, format_satoshis, json_decode, print_error, json_encode, is_hash256_str from .util import bfh, bh2u, format_satoshis, json_decode, json_encode, is_hash256_str
from . import bitcoin from . import bitcoin
from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS from .bitcoin import is_address, hash_160, COIN, TYPE_ADDRESS
from . import bip32 from . import bip32
@ -927,15 +927,14 @@ def add_network_options(parser):
def add_global_options(parser): def add_global_options(parser):
group = parser.add_argument_group('global options') group = parser.add_argument_group('global options')
# const is for when no argument is given to verbosity group.add_argument("-v", dest="verbosity", help="Set verbosity filter", default='')
# default is for when the flag is missing
group.add_argument("-v", dest="verbosity", help="Set verbosity filter", default='', const='*', nargs='?')
group.add_argument("-D", "--dir", dest="electrum_path", help="electrum directory") group.add_argument("-D", "--dir", dest="electrum_path", help="electrum directory")
group.add_argument("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum_data' directory") group.add_argument("-P", "--portable", action="store_true", dest="portable", default=False, help="Use local 'electrum_data' directory")
group.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path") group.add_argument("-w", "--wallet", dest="wallet_path", help="wallet path")
group.add_argument("--testnet", action="store_true", dest="testnet", default=False, help="Use Testnet") group.add_argument("--testnet", action="store_true", dest="testnet", default=False, help="Use Testnet")
group.add_argument("--regtest", action="store_true", dest="regtest", default=False, help="Use Regtest") group.add_argument("--regtest", action="store_true", dest="regtest", default=False, help="Use Regtest")
group.add_argument("--simnet", action="store_true", dest="simnet", default=False, help="Use Simnet") group.add_argument("--simnet", action="store_true", dest="simnet", default=False, help="Use Simnet")
group.add_argument("--disablefilelogging", action="store_true", dest="disablefilelogging", default=False, help="Do not log to file")
def get_parser(): def get_parser():
# create main parser # create main parser

8
electrum/contacts.py

@ -27,12 +27,14 @@ from dns.exception import DNSException
from . import bitcoin from . import bitcoin
from . import dnssec from . import dnssec
from .util import export_meta, import_meta, print_error, to_string from .util import export_meta, import_meta, to_string
from .logging import Logger
class Contacts(dict): class Contacts(dict, Logger):
def __init__(self, storage): def __init__(self, storage):
Logger.__init__(self)
self.storage = storage self.storage = storage
d = self.storage.get('contacts', {}) d = self.storage.get('contacts', {})
try: try:
@ -99,7 +101,7 @@ class Contacts(dict):
try: try:
records, validated = dnssec.query(url, dns.rdatatype.TXT) records, validated = dnssec.query(url, dns.rdatatype.TXT)
except DNSException as e: except DNSException as e:
print_error(f'Error resolving openalias: {repr(e)}') self.logger.info(f'Error resolving openalias: {repr(e)}')
return None return None
prefix = 'btc' prefix = 'btc'
for record in records: for record in records:

19
electrum/daemon.py

@ -36,7 +36,7 @@ import jsonrpclib
from .jsonrpc import VerifyingJSONRPCServer from .jsonrpc import VerifyingJSONRPCServer
from .version import ELECTRUM_VERSION from .version import ELECTRUM_VERSION
from .network import Network from .network import Network
from .util import (json_decode, DaemonThread, print_error, to_string, from .util import (json_decode, DaemonThread, to_string,
create_and_start_event_loop, profiler, standardize_path) create_and_start_event_loop, profiler, standardize_path)
from .wallet import Wallet, Abstract_Wallet from .wallet import Wallet, Abstract_Wallet
from .storage import WalletStorage from .storage import WalletStorage
@ -44,6 +44,10 @@ from .commands import known_commands, Commands
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
from .exchange_rate import FxThread from .exchange_rate import FxThread
from .plugin import run_hook from .plugin import run_hook
from .logging import get_logger
_logger = get_logger(__name__)
def get_lockfile(config: SimpleConfig): def get_lockfile(config: SimpleConfig):
@ -92,7 +96,7 @@ def get_server(config: SimpleConfig) -> Optional[jsonrpclib.Server]:
server.ping() server.ping()
return server return server
except Exception as e: except Exception as e:
print_error(f"failed to connect to JSON-RPC server: {e}") _logger.info(f"failed to connect to JSON-RPC server: {e}")
if not create_time or create_time < time.time() - 1.0: if not create_time or create_time < time.time() - 1.0:
return None return None
# Sleep a bit and try again; it might have just been started # Sleep a bit and try again; it might have just been started
@ -114,8 +118,7 @@ def get_rpc_credentials(config: SimpleConfig) -> Tuple[str, str]:
config.set_key('rpcuser', rpc_user) config.set_key('rpcuser', rpc_user)
config.set_key('rpcpassword', rpc_password, save=True) config.set_key('rpcpassword', rpc_password, save=True)
elif rpc_password == '': elif rpc_password == '':
from .util import print_stderr _logger.warning('RPC authentication is disabled.')
print_stderr('WARNING: RPC authentication is disabled.')
return rpc_user, rpc_password return rpc_user, rpc_password
@ -154,7 +157,7 @@ class Daemon(DaemonThread):
server = VerifyingJSONRPCServer((host, port), logRequests=False, server = VerifyingJSONRPCServer((host, port), logRequests=False,
rpc_user=rpc_user, rpc_password=rpc_password) rpc_user=rpc_user, rpc_password=rpc_password)
except Exception as e: except Exception as e:
self.print_error('Warning: cannot initialize RPC server on host', host, e) self.logger.error(f'cannot initialize RPC server on host {host}: {repr(e)}')
self.server = None self.server = None
os.close(fd) os.close(fd)
return return
@ -323,7 +326,7 @@ class Daemon(DaemonThread):
for k, wallet in self.wallets.items(): for k, wallet in self.wallets.items():
wallet.stop_threads() wallet.stop_threads()
if self.network: if self.network:
self.print_error("shutting down network") self.logger.info("shutting down network")
self.network.stop() self.network.stop()
# stop event loop # stop event loop
self.asyncio_loop.call_soon_threadsafe(self._stop_loop.set_result, 1) self.asyncio_loop.call_soon_threadsafe(self._stop_loop.set_result, 1)
@ -333,7 +336,7 @@ class Daemon(DaemonThread):
def stop(self): def stop(self):
if self.gui: if self.gui:
self.gui.stop() self.gui.stop()
self.print_error("stopping, removing lockfile") self.logger.info("stopping, removing lockfile")
remove_lockfile(get_lockfile(self.config)) remove_lockfile(get_lockfile(self.config))
DaemonThread.stop(self) DaemonThread.stop(self)
@ -347,5 +350,5 @@ class Daemon(DaemonThread):
try: try:
self.gui.main() self.gui.main()
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stdout) self.logger.exception('')
# app will exit now # app will exit now

8
electrum/dnssec.py

@ -173,7 +173,10 @@ dns.dnssec.validate = dns.dnssec._validate
from .util import print_error from .logging import get_logger
_logger = get_logger(__name__)
# hard-coded trust anchors (root KSKs) # hard-coded trust anchors (root KSKs)
@ -262,8 +265,7 @@ def query(url, rtype):
out = get_and_validate(ns, url, rtype) out = get_and_validate(ns, url, rtype)
validated = True validated = True
except BaseException as e: except BaseException as e:
#traceback.print_exc(file=sys.stderr) _logger.info(f"DNSSEC error: {str(e)}")
print_error("DNSSEC error:", str(e))
resolver = dns.resolver.get_default_resolver() resolver = dns.resolver.get_default_resolver()
out = resolver.query(url, rtype) out = resolver.query(url, rtype)
validated = False validated = False

6
electrum/ecc.py

@ -33,13 +33,15 @@ from ecdsa.curves import SECP256k1
from ecdsa.ellipticcurve import Point from ecdsa.ellipticcurve import Point
from ecdsa.util import string_to_number, number_to_string from ecdsa.util import string_to_number, number_to_string
from .util import bfh, bh2u, assert_bytes, print_error, to_bytes, InvalidPassword, profiler from .util import bfh, bh2u, assert_bytes, to_bytes, InvalidPassword, profiler
from .crypto import (sha256d, aes_encrypt_with_iv, aes_decrypt_with_iv, hmac_oneshot) from .crypto import (sha256d, aes_encrypt_with_iv, aes_decrypt_with_iv, hmac_oneshot)
from .ecc_fast import do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1 from .ecc_fast import do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1
from . import msqr from . import msqr
from . import constants from . import constants
from .logging import get_logger
_logger = get_logger(__name__)
do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1() do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1()
CURVE_ORDER = SECP256k1.order CURVE_ORDER = SECP256k1.order
@ -332,7 +334,7 @@ def verify_message_with_address(address: str, sig65: bytes, message: bytes, *, n
public_key.verify_message_hash(sig65[1:], h) public_key.verify_message_hash(sig65[1:], h)
return True return True
except Exception as e: except Exception as e:
print_error(f"Verification error: {repr(e)}") _logger.info(f"Verification error: {repr(e)}")
return False return False

19
electrum/ecc_fast.py

@ -12,7 +12,10 @@ from ctypes import (
import ecdsa import ecdsa
from .util import print_stderr, print_error from .logging import get_logger
_logger = get_logger(__name__)
SECP256K1_FLAGS_TYPE_MASK = ((1 << 8) - 1) SECP256K1_FLAGS_TYPE_MASK = ((1 << 8) - 1)
@ -44,7 +47,7 @@ def load_library():
secp256k1 = ctypes.cdll.LoadLibrary(library_path) secp256k1 = ctypes.cdll.LoadLibrary(library_path)
if not secp256k1: if not secp256k1:
print_stderr('[ecc] warning: libsecp256k1 library failed to load') _logger.warning('libsecp256k1 library failed to load')
return None return None
try: try:
@ -86,11 +89,10 @@ def load_library():
if r: if r:
return secp256k1 return secp256k1
else: else:
print_stderr('[ecc] warning: secp256k1_context_randomize failed') _logger.warning('secp256k1_context_randomize failed')
return None return None
except (OSError, AttributeError): except (OSError, AttributeError):
#traceback.print_exc(file=sys.stderr) _logger.warning('libsecp256k1 library was found and loaded but there was an error when using it')
print_stderr('[ecc] warning: libsecp256k1 library was found and loaded but there was an error when using it')
return None return None
@ -184,9 +186,9 @@ def _prepare_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1():
def do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1(): def do_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1():
if not _libsecp256k1: if not _libsecp256k1:
# FIXME print_error will always print as 'verbosity' is not yet initialised # FIXME logging 'verbosity' is not yet initialised
print_error('[ecc] info: libsecp256k1 library not available, falling back to python-ecdsa. ' _logger.info('libsecp256k1 library not available, falling back to python-ecdsa. '
'This means signing operations will be slower.') 'This means signing operations will be slower.')
return return
if not _patched_functions.prepared_to_patch: if not _patched_functions.prepared_to_patch:
raise Exception("can't patch python-ecdsa without preparations") raise Exception("can't patch python-ecdsa without preparations")
@ -218,6 +220,5 @@ try:
_libsecp256k1 = load_library() _libsecp256k1 = load_library()
except: except:
_libsecp256k1 = None _libsecp256k1 = None
#traceback.print_exc(file=sys.stderr)
_prepare_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1() _prepare_monkey_patching_of_python_ecdsa_internals_with_libsecp256k1()

24
electrum/exchange_rate.py

@ -8,17 +8,17 @@ import time
import csv import csv
import decimal import decimal
from decimal import Decimal from decimal import Decimal
import traceback
from typing import Sequence, Optional from typing import Sequence, Optional
from aiorpcx.curio import timeout_after, TaskTimeout, TaskGroup from aiorpcx.curio import timeout_after, TaskTimeout, TaskGroup
from .bitcoin import COIN from .bitcoin import COIN
from .i18n import _ from .i18n import _
from .util import (PrintError, ThreadJob, make_dir, log_exceptions, from .util import (ThreadJob, make_dir, log_exceptions,
make_aiohttp_session, resource_path) make_aiohttp_session, resource_path)
from .network import Network from .network import Network
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
from .logging import Logger
DEFAULT_ENABLED = False DEFAULT_ENABLED = False
@ -35,9 +35,10 @@ CCY_PRECISIONS = {'BHD': 3, 'BIF': 0, 'BYR': 0, 'CLF': 4, 'CLP': 0,
'VUV': 0, 'XAF': 0, 'XAU': 4, 'XOF': 0, 'XPF': 0} 'VUV': 0, 'XAF': 0, 'XAU': 4, 'XOF': 0, 'XPF': 0}
class ExchangeBase(PrintError): class ExchangeBase(Logger):
def __init__(self, on_quotes, on_history): def __init__(self, on_quotes, on_history):
Logger.__init__(self)
self.history = {} self.history = {}
self.quotes = {} self.quotes = {}
self.on_quotes = on_quotes self.on_quotes = on_quotes
@ -74,12 +75,11 @@ class ExchangeBase(PrintError):
async def update_safe(self, ccy): async def update_safe(self, ccy):
try: try:
self.print_error("getting fx quotes for", ccy) self.logger.info(f"getting fx quotes for {ccy}")
self.quotes = await self.get_rates(ccy) self.quotes = await self.get_rates(ccy)
self.print_error("received fx quotes") self.logger.info("received fx quotes")
except BaseException as e: except BaseException as e:
self.print_error("failed fx quotes:", repr(e)) self.logger.info(f"failed fx quotes: {repr(e)}")
# traceback.print_exc()
self.quotes = {} self.quotes = {}
self.on_quotes() self.on_quotes()
@ -103,12 +103,11 @@ class ExchangeBase(PrintError):
@log_exceptions @log_exceptions
async def get_historical_rates_safe(self, ccy, cache_dir): async def get_historical_rates_safe(self, ccy, cache_dir):
try: try:
self.print_error(f"requesting fx history for {ccy}") self.logger.info(f"requesting fx history for {ccy}")
h = await self.request_history(ccy) h = await self.request_history(ccy)
self.print_error(f"received fx history for {ccy}") self.logger.info(f"received fx history for {ccy}")
except BaseException as e: except BaseException as e:
self.print_error(f"failed fx history: {repr(e)}") self.logger.info(f"failed fx history: {repr(e)}")
#traceback.print_exc()
return return
filename = os.path.join(cache_dir, self.name() + '_' + ccy) filename = os.path.join(cache_dir, self.name() + '_' + ccy)
with open(filename, 'w', encoding='utf-8') as f: with open(filename, 'w', encoding='utf-8') as f:
@ -458,6 +457,7 @@ def get_exchanges_by_ccy(history=True):
class FxThread(ThreadJob): class FxThread(ThreadJob):
def __init__(self, config: SimpleConfig, network: Network): def __init__(self, config: SimpleConfig, network: Network):
ThreadJob.__init__(self)
self.config = config self.config = config
self.network = network self.network = network
if self.network: if self.network:
@ -560,7 +560,7 @@ class FxThread(ThreadJob):
def set_exchange(self, name): def set_exchange(self, name):
class_ = globals().get(name) or globals().get(DEFAULT_EXCHANGE) class_ = globals().get(name) or globals().get(DEFAULT_EXCHANGE)
self.print_error("using exchange", name) self.logger.info(f"using exchange {name}")
if self.config_exchange() != name: if self.config_exchange() != name:
self.config.set_key('use_exchange', name, True) self.config.set_key('use_exchange', name, True)
self.exchange = class_(self.on_quotes, self.on_history) self.exchange = class_(self.on_quotes, self.on_history)

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

@ -13,6 +13,7 @@ from kivy.utils import platform
from electrum.gui.kivy.i18n import _ from electrum.gui.kivy.i18n import _
from electrum.base_crash_reporter import BaseCrashReporter from electrum.base_crash_reporter import BaseCrashReporter
from electrum.logging import Logger
Builder.load_string(''' Builder.load_string('''
@ -172,9 +173,10 @@ class CrashReportDetails(Factory.Popup):
print(text) print(text)
class ExceptionHook(base.ExceptionHandler): class ExceptionHook(base.ExceptionHandler, Logger):
def __init__(self, main_window): def __init__(self, main_window):
super().__init__() base.ExceptionHandler.__init__(self)
Logger.__init__(self)
self.main_window = main_window self.main_window = main_window
if not main_window.electrum_config.get(BaseCrashReporter.config_key, default=True): if not main_window.electrum_config.get(BaseCrashReporter.config_key, default=True):
return return
@ -185,6 +187,7 @@ class ExceptionHook(base.ExceptionHandler):
def handle_exception(self, _inst): def handle_exception(self, _inst):
exc_info = sys.exc_info() exc_info = sys.exc_info()
self.logger.error('exception caught by crash reporter', exc_info=exc_info)
# Check if this is an exception from within the exception handler: # Check if this is an exception from within the exception handler:
import traceback import traceback
for item in traceback.extract_tb(exc_info[2]): for item in traceback.extract_tb(exc_info[2]):

18
electrum/gui/qt/__init__.py

@ -45,9 +45,10 @@ import PyQt5.QtCore as QtCore
from electrum.i18n import _, set_language from electrum.i18n import _, set_language
from electrum.plugin import run_hook from electrum.plugin import run_hook
from electrum.base_wizard import GoBack from electrum.base_wizard import GoBack
from electrum.util import (UserCancelled, PrintError, profiler, from electrum.util import (UserCancelled, profiler,
WalletFileException, BitcoinException, get_new_wallet_name) WalletFileException, BitcoinException, get_new_wallet_name)
from electrum.wallet import Wallet, Abstract_Wallet from electrum.wallet import Wallet, Abstract_Wallet
from electrum.logging import Logger
from .installwizard import InstallWizard, WalletAlreadyOpenInMemory from .installwizard import InstallWizard, WalletAlreadyOpenInMemory
@ -78,11 +79,12 @@ class QNetworkUpdatedSignalObject(QObject):
network_updated_signal = pyqtSignal(str, object) network_updated_signal = pyqtSignal(str, object)
class ElectrumGui(PrintError): class ElectrumGui(Logger):
@profiler @profiler
def __init__(self, config, daemon, plugins): def __init__(self, config, daemon, plugins):
set_language(config.get('language', get_default_language())) set_language(config.get('language', get_default_language()))
Logger.__init__(self)
# Uncomment this call to verify objects are being properly # Uncomment this call to verify objects are being properly
# GC-ed when windows are closed # GC-ed when windows are closed
#network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer, #network.add_jobs([DebugMem([Abstract_Wallet, SPV, Synchronizer,
@ -129,7 +131,7 @@ class ElectrumGui(PrintError):
self.app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5()) self.app.setStyleSheet(qdarkstyle.load_stylesheet_pyqt5())
except BaseException as e: except BaseException as e:
use_dark_theme = False use_dark_theme = False
self.print_error('Error setting dark theme: {}'.format(repr(e))) self.logger.warning(f'Error setting dark theme: {repr(e)}')
# Even if we ourselves don't set the dark theme, # Even if we ourselves don't set the dark theme,
# the OS/window manager/etc might set *a dark theme*. # the OS/window manager/etc might set *a dark theme*.
# Hence, try to choose colors accordingly: # Hence, try to choose colors accordingly:
@ -221,7 +223,7 @@ class ElectrumGui(PrintError):
try: try:
wallet = self.daemon.load_wallet(path, None) wallet = self.daemon.load_wallet(path, None)
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stdout) self.logger.exception('')
QMessageBox.warning(None, _('Error'), QMessageBox.warning(None, _('Error'),
_('Cannot load wallet') + ' (1):\n' + str(e)) _('Cannot load wallet') + ' (1):\n' + str(e))
# if app is starting, still let wizard to appear # if app is starting, still let wizard to appear
@ -239,7 +241,7 @@ class ElectrumGui(PrintError):
else: else:
window = self._create_window_for_wallet(wallet) window = self._create_window_for_wallet(wallet)
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stdout) self.logger.exception('')
QMessageBox.warning(None, _('Error'), QMessageBox.warning(None, _('Error'),
_('Cannot create window for wallet') + ':\n' + str(e)) _('Cannot create window for wallet') + ':\n' + str(e))
if app_is_starting: if app_is_starting:
@ -271,7 +273,7 @@ class ElectrumGui(PrintError):
except WalletAlreadyOpenInMemory as e: except WalletAlreadyOpenInMemory as e:
return e.wallet return e.wallet
except (WalletFileException, BitcoinException) as e: except (WalletFileException, BitcoinException) as e:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
QMessageBox.warning(None, _('Error'), QMessageBox.warning(None, _('Error'),
_('Cannot load wallet') + ' (2):\n' + str(e)) _('Cannot load wallet') + ' (2):\n' + str(e))
return return
@ -311,7 +313,7 @@ class ElectrumGui(PrintError):
except GoBack: except GoBack:
return return
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stdout) self.logger.exception('')
return return
self.timer.start() self.timer.start()
self.config.open_last_wallet() self.config.open_last_wallet()
@ -346,5 +348,5 @@ class ElectrumGui(PrintError):
# on some platforms the exec_ call may not return, so use clean_up() # on some platforms the exec_ call may not return, so use clean_up()
def stop(self): def stop(self):
self.print_error('closing GUI') self.logger.info('closing GUI')
self.app.quit() self.app.quit()

18
electrum/gui/qt/exception_window.py

@ -32,10 +32,11 @@ from PyQt5.QtWidgets import (QWidget, QLabel, QPushButton, QTextEdit,
from electrum.i18n import _ from electrum.i18n import _
from electrum.base_crash_reporter import BaseCrashReporter from electrum.base_crash_reporter import BaseCrashReporter
from electrum.logging import Logger
from .util import MessageBoxMixin, read_QIcon from .util import MessageBoxMixin, read_QIcon
class Exception_Window(BaseCrashReporter, QWidget, MessageBoxMixin): class Exception_Window(BaseCrashReporter, QWidget, MessageBoxMixin, Logger):
_active_window = None _active_window = None
def __init__(self, main_window, exctype, value, tb): def __init__(self, main_window, exctype, value, tb):
@ -46,6 +47,8 @@ class Exception_Window(BaseCrashReporter, QWidget, MessageBoxMixin):
self.setWindowTitle('Electrum - ' + _('An Error Occurred')) self.setWindowTitle('Electrum - ' + _('An Error Occurred'))
self.setMinimumSize(600, 300) self.setMinimumSize(600, 300)
Logger.__init__(self)
main_box = QVBoxLayout() main_box = QVBoxLayout()
heading = QLabel('<h2>' + BaseCrashReporter.CRASH_TITLE + '</h2>') heading = QLabel('<h2>' + BaseCrashReporter.CRASH_TITLE + '</h2>')
@ -95,7 +98,7 @@ class Exception_Window(BaseCrashReporter, QWidget, MessageBoxMixin):
proxy = self.main_window.network.proxy proxy = self.main_window.network.proxy
response = BaseCrashReporter.send_report(self, self.main_window.network.asyncio_loop, proxy) response = BaseCrashReporter.send_report(self, self.main_window.network.asyncio_loop, proxy)
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stderr) self.logger.exception('There was a problem with the automatic reporting')
self.main_window.show_critical(_('There was a problem with the automatic reporting:') + '\n' + self.main_window.show_critical(_('There was a problem with the automatic reporting:') + '\n' +
str(e) + '\n' + str(e) + '\n' +
_("Please report this issue manually.")) _("Please report this issue manually."))
@ -105,7 +108,6 @@ class Exception_Window(BaseCrashReporter, QWidget, MessageBoxMixin):
def on_close(self): def on_close(self):
Exception_Window._active_window = None Exception_Window._active_window = None
sys.__excepthook__(*self.exc_args)
self.close() self.close()
def show_never(self): def show_never(self):
@ -131,16 +133,18 @@ def _show_window(*args):
Exception_Window._active_window = Exception_Window(*args) Exception_Window._active_window = Exception_Window(*args)
class Exception_Hook(QObject): class Exception_Hook(QObject, Logger):
_report_exception = QtCore.pyqtSignal(object, object, object, object) _report_exception = QtCore.pyqtSignal(object, object, object, object)
def __init__(self, main_window, *args, **kwargs): def __init__(self, main_window, *args, **kwargs):
super(Exception_Hook, self).__init__(*args, **kwargs) QObject.__init__(self, *args, **kwargs)
Logger.__init__(self)
if not main_window.config.get(BaseCrashReporter.config_key, default=True): if not main_window.config.get(BaseCrashReporter.config_key, default=True):
return return
self.main_window = main_window self.main_window = main_window
sys.excepthook = self.handler sys.excepthook = self.handler
self._report_exception.connect(_show_window) self._report_exception.connect(_show_window)
def handler(self, *args): def handler(self, *exc_info):
self._report_exception.emit(self.main_window, *args) self.logger.error('exception caught by crash reporter', exc_info=exc_info)
self._report_exception.emit(self.main_window, *exc_info)

18
electrum/gui/qt/history_list.py

@ -41,8 +41,9 @@ from PyQt5.QtWidgets import (QMenu, QHeaderView, QLabel, QMessageBox,
from electrum.address_synchronizer import TX_HEIGHT_LOCAL from electrum.address_synchronizer import TX_HEIGHT_LOCAL
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import (block_explorer_URL, profiler, print_error, TxMinedInfo, from electrum.util import (block_explorer_URL, profiler, TxMinedInfo,
OrderedDictWithIndex, PrintError, timestamp_to_datetime) OrderedDictWithIndex, timestamp_to_datetime)
from electrum.logging import get_logger, Logger
from .util import (read_QIcon, MONOSPACE_FONT, Buttons, CancelButton, OkButton, from .util import (read_QIcon, MONOSPACE_FONT, Buttons, CancelButton, OkButton,
filename_field, MyTreeView, AcceptFileDragDrop, WindowModalDialog, filename_field, MyTreeView, AcceptFileDragDrop, WindowModalDialog,
@ -51,10 +52,14 @@ from .util import (read_QIcon, MONOSPACE_FONT, Buttons, CancelButton, OkButton,
if TYPE_CHECKING: if TYPE_CHECKING:
from electrum.wallet import Abstract_Wallet from electrum.wallet import Abstract_Wallet
_logger = get_logger(__name__)
try: try:
from electrum.plot import plot_history, NothingToPlotException from electrum.plot import plot_history, NothingToPlotException
except: except:
print_error("qt/history_list: could not import electrum.plot. This feature needs matplotlib to be installed.") _logger.info("could not import electrum.plot. This feature needs matplotlib to be installed.")
plot_history = None plot_history = None
# note: this list needs to be kept in sync with another in kivy # note: this list needs to be kept in sync with another in kivy
@ -97,10 +102,11 @@ class HistorySortModel(QSortFilterProxyModel):
except: except:
return False return False
class HistoryModel(QAbstractItemModel, PrintError): class HistoryModel(QAbstractItemModel, Logger):
def __init__(self, parent): def __init__(self, parent):
super().__init__(parent) QAbstractItemModel.__init__(self, parent)
Logger.__init__(self)
self.parent = parent self.parent = parent
self.view = None # type: HistoryList self.view = None # type: HistoryList
self.transactions = OrderedDictWithIndex() self.transactions = OrderedDictWithIndex()
@ -224,7 +230,7 @@ class HistoryModel(QAbstractItemModel, PrintError):
@profiler @profiler
def refresh(self, reason: str): def refresh(self, reason: str):
self.print_error(f"refreshing... reason: {reason}") self.logger.info(f"refreshing... reason: {reason}")
assert self.parent.gui_thread == threading.current_thread(), 'must be called from GUI thread' assert self.parent.gui_thread == threading.current_thread(), 'must be called from GUI thread'
assert self.view, 'view not set' assert self.view, 'view not set'
selected = self.view.selectionModel().currentIndex() selected = self.view.selectionModel().currentIndex()

10
electrum/gui/qt/installwizard.py

@ -116,8 +116,8 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
accept_signal = pyqtSignal() accept_signal = pyqtSignal()
def __init__(self, config, app, plugins): def __init__(self, config, app, plugins):
BaseWizard.__init__(self, config, plugins)
QDialog.__init__(self, None) QDialog.__init__(self, None)
BaseWizard.__init__(self, config, plugins)
self.setWindowTitle('Electrum - ' + _('Install Wizard')) self.setWindowTitle('Electrum - ' + _('Install Wizard'))
self.app = app self.app = app
self.config = config self.config = config
@ -209,7 +209,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
self.temp_storage = WalletStorage(path, manual_upgrades=True) self.temp_storage = WalletStorage(path, manual_upgrades=True)
self.next_button.setEnabled(True) self.next_button.setEnabled(True)
except BaseException: except BaseException:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
self.temp_storage = None self.temp_storage = None
self.next_button.setEnabled(False) self.next_button.setEnabled(False)
user_needs_to_enter_password = False user_needs_to_enter_password = False
@ -266,7 +266,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
QMessageBox.information(None, _('Error'), str(e)) QMessageBox.information(None, _('Error'), str(e))
continue continue
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stdout) self.logger.exception('')
QMessageBox.information(None, _('Error'), str(e)) QMessageBox.information(None, _('Error'), str(e))
raise UserCancelled() raise UserCancelled()
elif self.temp_storage.is_encrypted_with_hw_device(): elif self.temp_storage.is_encrypted_with_hw_device():
@ -280,7 +280,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
self.reset_stack() self.reset_stack()
return self.select_storage(path, get_wallet_from_daemon) return self.select_storage(path, get_wallet_from_daemon)
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stdout) self.logger.exception('')
QMessageBox.information(None, _('Error'), str(e)) QMessageBox.information(None, _('Error'), str(e))
raise UserCancelled() raise UserCancelled()
if self.temp_storage.is_past_initial_decryption(): if self.temp_storage.is_past_initial_decryption():
@ -337,7 +337,7 @@ class InstallWizard(QDialog, MessageBoxMixin, BaseWizard):
def on_error(self, exc_info): def on_error(self, exc_info):
if not isinstance(exc_info[1], UserCancelled): if not isinstance(exc_info[1], UserCancelled):
traceback.print_exception(*exc_info) self.logger.error("on_error", exc_info=exc_info)
self.show_error(str(exc_info[1])) self.show_error(str(exc_info[1]))
def set_icon(self, filename): def set_icon(self, filename):

42
electrum/gui/qt/main_window.py

@ -54,7 +54,7 @@ from electrum.bitcoin import COIN, is_address, TYPE_ADDRESS
from electrum.plugin import run_hook from electrum.plugin import run_hook
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import (format_time, format_satoshis, format_fee_satoshis, from electrum.util import (format_time, format_satoshis, format_fee_satoshis,
format_satoshis_plain, NotEnoughFunds, PrintError, format_satoshis_plain, NotEnoughFunds,
UserCancelled, NoDynamicFeeEstimates, profiler, UserCancelled, NoDynamicFeeEstimates, profiler,
export_meta, import_meta, bh2u, bfh, InvalidPassword, export_meta, import_meta, bh2u, bfh, InvalidPassword,
base_units, base_units_list, base_unit_name_to_decimal_point, base_units, base_units_list, base_unit_name_to_decimal_point,
@ -69,6 +69,7 @@ from electrum.version import ELECTRUM_VERSION
from electrum.network import Network, TxBroadcastError, BestEffortRequestFailed from electrum.network import Network, TxBroadcastError, BestEffortRequestFailed
from electrum.exchange_rate import FxThread from electrum.exchange_rate import FxThread
from electrum.simple_config import SimpleConfig from electrum.simple_config import SimpleConfig
from electrum.logging import Logger
from .exception_window import Exception_Hook from .exception_window import Exception_Hook
from .amountedit import AmountEdit, BTCAmountEdit, MyLineEdit, FeerateEdit from .amountedit import AmountEdit, BTCAmountEdit, MyLineEdit, FeerateEdit
@ -110,7 +111,7 @@ class StatusBarButton(QPushButton):
from electrum.paymentrequest import PR_PAID from electrum.paymentrequest import PR_PAID
class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError): class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
payment_request_ok_signal = pyqtSignal() payment_request_ok_signal = pyqtSignal()
payment_request_error_signal = pyqtSignal() payment_request_error_signal = pyqtSignal()
@ -147,6 +148,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.require_fee_update = False self.require_fee_update = False
self.tl_windows = [] self.tl_windows = []
self.tx_external_keypairs = {} self.tx_external_keypairs = {}
Logger.__init__(self)
self.tx_notification_queue = queue.Queue() self.tx_notification_queue = queue.Queue()
self.tx_notification_last_time = 0 self.tx_notification_last_time = 0
@ -320,8 +322,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
return self.top_level_window_recurse(override, test_func) return self.top_level_window_recurse(override, test_func)
def diagnostic_name(self): def diagnostic_name(self):
return "%s/%s" % (PrintError.diagnostic_name(self), #return '{}:{}'.format(self.__class__.__name__, self.wallet.diagnostic_name())
self.wallet.basename() if self.wallet else "None") return self.wallet.diagnostic_name()
def is_hidden(self): def is_hidden(self):
return self.isMinimized() or self.isHidden() return self.isMinimized() or self.isHidden()
@ -344,7 +346,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.show_error(str(e)) self.show_error(str(e))
else: else:
try: try:
traceback.print_exception(*exc_info) self.logger.error("on_error", exc_info=exc_info)
except OSError: except OSError:
pass # see #4418 pass # see #4418
self.show_error(str(e)) self.show_error(str(e))
@ -369,7 +371,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
# Handle in GUI thread # Handle in GUI thread
self.network_signal.emit(event, args) self.network_signal.emit(event, args)
else: else:
self.print_error("unexpected network message:", event, args) self.logger.info(f"unexpected network message: {event} {args}")
def on_network_qt(self, event, args=None): def on_network_qt(self, event, args=None):
# Handle a network message in the GUI thread # Handle a network message in the GUI thread
@ -391,7 +393,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.require_fee_update = True self.require_fee_update = True
self.history_model.on_fee_histogram() self.history_model.on_fee_histogram()
else: else:
self.print_error("unexpected network_qt signal:", event, args) self.logger.info(f"unexpected network_qt signal: {event} {args}")
def fetch_alias(self): def fetch_alias(self):
self.alias_info = None self.alias_info = None
@ -407,7 +409,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
def close_wallet(self): def close_wallet(self):
if self.wallet: if self.wallet:
self.print_error('close_wallet', self.wallet.storage.path) self.logger.info(f'close_wallet {self.wallet.storage.path}')
run_hook('close_wallet', self.wallet) run_hook('close_wallet', self.wallet)
@profiler @profiler
@ -444,7 +446,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
assert screen.contains(QRect(*winpos)) assert screen.contains(QRect(*winpos))
self.setGeometry(*winpos) self.setGeometry(*winpos)
except: except:
self.print_error("using default geometry") self.logger.info("using default geometry")
self.setGeometry(100, 100, 840, 400) self.setGeometry(100, 100, 840, 400)
def watching_only_changed(self): def watching_only_changed(self):
@ -683,7 +685,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
if self.tx_notification_last_time + rate_limit > now: if self.tx_notification_last_time + rate_limit > now:
return return
self.tx_notification_last_time = now self.tx_notification_last_time = now
self.print_error("Notifying GUI about new transactions") self.logger.info("Notifying GUI about new transactions")
txns = [] txns = []
while True: while True:
try: try:
@ -1040,7 +1042,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
try: try:
self.wallet.add_payment_request(req, self.config) self.wallet.add_payment_request(req, self.config)
except Exception as e: except Exception as e:
traceback.print_exc(file=sys.stderr) self.logger.exception('Error adding payment request')
self.show_error(_('Error adding payment request') + ':\n' + str(e)) self.show_error(_('Error adding payment request') + ':\n' + str(e))
else: else:
self.sign_payment_request(addr) self.sign_payment_request(addr)
@ -1447,7 +1449,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
pass pass
return return
except BaseException: except BaseException:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
return return
size = tx.estimated_size() size = tx.estimated_size()
@ -1636,7 +1638,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.show_error(str(e)) self.show_error(str(e))
raise raise
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stdout) self.logger.exception('')
self.show_message(str(e)) self.show_message(str(e))
return return
@ -1745,7 +1747,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
coro = pr.send_payment_and_receive_paymentack(str(tx), refund_address) coro = pr.send_payment_and_receive_paymentack(str(tx), refund_address)
fut = asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop) fut = asyncio.run_coroutine_threadsafe(coro, self.network.asyncio_loop)
ack_status, ack_msg = fut.result(timeout=20) ack_status, ack_msg = fut.result(timeout=20)
self.print_error(f"Payment ACK: {ack_status}. Ack message: {ack_msg}") self.logger.info(f"Payment ACK: {ack_status}. Ack message: {ack_msg}")
return status, msg return status, msg
# Capture current TL window; override might be removed on return # Capture current TL window; override might be removed on return
@ -2123,7 +2125,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
except UserCancelled: except UserCancelled:
return return
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
self.show_error(str(e)) self.show_error(str(e))
return return
old_password = hw_dev_pw if self.wallet.has_password() else None old_password = hw_dev_pw if self.wallet.has_password() else None
@ -2141,7 +2143,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
self.show_error(str(e)) self.show_error(str(e))
return return
except BaseException: except BaseException:
traceback.print_exc(file=sys.stdout) self.logger.exception('Failed to update password')
self.show_error(_('Failed to update password')) self.show_error(_('Failed to update password'))
return return
msg = _('Password was updated successfully') if self.wallet.has_password() else _('Password is disabled, this wallet is not protected') msg = _('Password was updated successfully') if self.wallet.has_password() else _('Password is disabled, this wallet is not protected')
@ -2279,7 +2281,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
try: try:
pk, redeem_script = self.wallet.export_private_key(address, password) pk, redeem_script = self.wallet.export_private_key(address, password)
except Exception as e: except Exception as e:
traceback.print_exc(file=sys.stdout) self.logger.exception('')
self.show_message(str(e)) self.show_message(str(e))
return return
xtype = bitcoin.deserialize_privkey(pk)[0] xtype = bitcoin.deserialize_privkey(pk)[0]
@ -2415,7 +2417,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
try: try:
public_key = ecc.ECPubkey(bfh(pubkey_e.text())) public_key = ecc.ECPubkey(bfh(pubkey_e.text()))
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stdout) self.logger.exception('Invalid Public key')
self.show_warning(_('Invalid Public key')) self.show_warning(_('Invalid Public key'))
return return
encrypted = public_key.encrypt_message(message) encrypted = public_key.encrypt_message(message)
@ -2725,7 +2727,6 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
try: try:
coins, keypairs = sweep_preparations(get_pk(), self.network) coins, keypairs = sweep_preparations(get_pk(), self.network)
except Exception as e: # FIXME too broad... except Exception as e: # FIXME too broad...
#traceback.print_exc(file=sys.stderr)
self.show_message(str(e)) self.show_message(str(e))
return return
self.do_clear() self.do_clear()
@ -3299,8 +3300,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
msg += '\n\n' + _('Requires') + ':\n' + '\n'.join(map(lambda x: x[1], descr.get('requires'))) msg += '\n\n' + _('Requires') + ':\n' + '\n'.join(map(lambda x: x[1], descr.get('requires')))
grid.addWidget(HelpButton(msg), i, 2) grid.addWidget(HelpButton(msg), i, 2)
except Exception: except Exception:
self.print_msg("error: cannot display plugin", name) self.logger.exception(f"cannot display plugin {name}")
traceback.print_exc(file=sys.stdout)
grid.setRowStretch(len(plugins.descriptions.values()), 1) grid.setRowStretch(len(plugins.descriptions.values()), 1)
vbox.addLayout(Buttons(CloseButton(d))) vbox.addLayout(Buttons(CloseButton(d)))
d.exec_() d.exec_()

7
electrum/gui/qt/network_dialog.py

@ -34,12 +34,15 @@ from PyQt5.QtWidgets import (QTreeWidget, QTreeWidgetItem, QMenu, QGridLayout, Q
from electrum.i18n import _ from electrum.i18n import _
from electrum import constants, blockchain from electrum import constants, blockchain
from electrum.util import print_error
from electrum.interface import serialize_server, deserialize_server from electrum.interface import serialize_server, deserialize_server
from electrum.network import Network from electrum.network import Network
from electrum.logging import get_logger
from .util import Buttons, CloseButton, HelpButton, read_QIcon from .util import Buttons, CloseButton, HelpButton, read_QIcon
_logger = get_logger(__name__)
protocol_names = ['TCP', 'SSL'] protocol_names = ['TCP', 'SSL']
protocol_letters = 'ts' protocol_letters = 'ts'
@ -491,7 +494,7 @@ class NetworkChoiceLayout(object):
else: else:
socks5_mode_index = self.proxy_mode.findText('SOCKS5') socks5_mode_index = self.proxy_mode.findText('SOCKS5')
if socks5_mode_index == -1: if socks5_mode_index == -1:
print_error("[network_dialog] can't find proxy_mode 'SOCKS5'") _logger.info("can't find proxy_mode 'SOCKS5'")
return return
self.proxy_mode.setCurrentIndex(socks5_mode_index) self.proxy_mode.setCurrentIndex(socks5_mode_index)
self.proxy_host.setText("127.0.0.1") self.proxy_host.setText("127.0.0.1")

8
electrum/gui/qt/paytoedit.py

@ -29,9 +29,10 @@ from decimal import Decimal
from PyQt5.QtGui import QFontMetrics from PyQt5.QtGui import QFontMetrics
from electrum import bitcoin from electrum import bitcoin
from electrum.util import bfh, PrintError from electrum.util import bfh
from electrum.transaction import TxOutput, push_script from electrum.transaction import TxOutput, push_script
from electrum.bitcoin import opcodes from electrum.bitcoin import opcodes
from electrum.logging import Logger
from .qrtextedit import ScanQRTextEdit from .qrtextedit import ScanQRTextEdit
from .completion_text_edit import CompletionTextEdit from .completion_text_edit import CompletionTextEdit
@ -43,11 +44,12 @@ frozen_style = "QWidget { background-color:none; border:none;}"
normal_style = "QPlainTextEdit { }" normal_style = "QPlainTextEdit { }"
class PayToEdit(CompletionTextEdit, ScanQRTextEdit, PrintError): class PayToEdit(CompletionTextEdit, ScanQRTextEdit, Logger):
def __init__(self, win): def __init__(self, win):
CompletionTextEdit.__init__(self) CompletionTextEdit.__init__(self)
ScanQRTextEdit.__init__(self) ScanQRTextEdit.__init__(self)
Logger.__init__(self)
self.win = win self.win = win
self.amount_edit = win.amount_e self.amount_edit = win.amount_e
self.document().contentsChanged.connect(self.update_size) self.document().contentsChanged.connect(self.update_size)
@ -226,7 +228,7 @@ class PayToEdit(CompletionTextEdit, ScanQRTextEdit, PrintError):
try: try:
data = self.win.contacts.resolve(key) data = self.win.contacts.resolve(key)
except Exception as e: except Exception as e:
self.print_error(f'error resolving address/alias: {repr(e)}') self.logger.info(f'error resolving address/alias: {repr(e)}')
return return
if not data: if not data:
return return

4
electrum/gui/qt/transaction_dialog.py

@ -42,6 +42,7 @@ from electrum.plugin import run_hook
from electrum import simple_config from electrum import simple_config
from electrum.util import bfh from electrum.util import bfh
from electrum.transaction import SerializationError, Transaction from electrum.transaction import SerializationError, Transaction
from electrum.logging import get_logger
from .util import (MessageBoxMixin, read_QIcon, Buttons, CopyButton, from .util import (MessageBoxMixin, read_QIcon, Buttons, CopyButton,
MONOSPACE_FONT, ColorScheme, ButtonsLineEdit) MONOSPACE_FONT, ColorScheme, ButtonsLineEdit)
@ -51,6 +52,7 @@ SAVE_BUTTON_ENABLED_TOOLTIP = _("Save transaction offline")
SAVE_BUTTON_DISABLED_TOOLTIP = _("Please sign this transaction in order to save it") SAVE_BUTTON_DISABLED_TOOLTIP = _("Please sign this transaction in order to save it")
_logger = get_logger(__name__)
dialogs = [] # Otherwise python randomly garbage collects the dialogs... dialogs = [] # Otherwise python randomly garbage collects the dialogs...
@ -58,7 +60,7 @@ def show_transaction(tx, parent, desc=None, prompt_if_unsaved=False):
try: try:
d = TxDialog(tx, parent, desc, prompt_if_unsaved) d = TxDialog(tx, parent, desc, prompt_if_unsaved)
except SerializationError as e: except SerializationError as e:
traceback.print_exc(file=sys.stderr) _logger.exception('unable to deserialize the transaction')
parent.show_critical(_("Electrum was unable to deserialize the transaction:") + "\n" + str(e)) parent.show_critical(_("Electrum was unable to deserialize the transaction:") + "\n" + str(e))
else: else:
dialogs.append(d) dialogs.append(d)

15
electrum/gui/qt/update_checker.py

@ -14,10 +14,11 @@ from electrum import version
from electrum import constants from electrum import constants
from electrum import ecc from electrum import ecc
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import PrintError, make_aiohttp_session from electrum.util import make_aiohttp_session
from electrum.logging import Logger
class UpdateCheck(QWidget, PrintError): class UpdateCheck(QWidget, Logger):
url = "https://electrum.org/version" url = "https://electrum.org/version"
download_url = "https://electrum.org/#download" download_url = "https://electrum.org/#download"
@ -92,12 +93,13 @@ class UpdateCheck(QWidget, PrintError):
self.detail_label.setText(_("Please wait while Electrum checks for available updates.")) self.detail_label.setText(_("Please wait while Electrum checks for available updates."))
class UpdateCheckThread(QThread, PrintError): class UpdateCheckThread(QThread, Logger):
checked = pyqtSignal(object) checked = pyqtSignal(object)
failed = pyqtSignal() failed = pyqtSignal()
def __init__(self, main_window): def __init__(self, main_window):
super().__init__() QThread.__init__(self)
Logger.__init__(self)
self.main_window = main_window self.main_window = main_window
async def get_update_info(self): async def get_update_info(self):
@ -120,7 +122,7 @@ class UpdateCheckThread(QThread, PrintError):
msg = version_num.encode('utf-8') msg = version_num.encode('utf-8')
if ecc.verify_message_with_address(address=address, sig65=sig, message=msg, if ecc.verify_message_with_address(address=address, sig65=sig, message=msg,
net=constants.BitcoinMainnet): net=constants.BitcoinMainnet):
self.print_error(f"valid sig for version announcement '{version_num}' from address '{address}'") self.logger.info(f"valid sig for version announcement '{version_num}' from address '{address}'")
break break
else: else:
raise Exception('no valid signature for version announcement') raise Exception('no valid signature for version announcement')
@ -134,8 +136,7 @@ class UpdateCheckThread(QThread, PrintError):
try: try:
update_info = asyncio.run_coroutine_threadsafe(self.get_update_info(), network.asyncio_loop).result() update_info = asyncio.run_coroutine_threadsafe(self.get_update_info(), network.asyncio_loop).result()
except Exception as e: except Exception as e:
#self.print_error(traceback.format_exc()) self.logger.info(f"got exception: '{repr(e)}'")
self.print_error(f"got exception: '{repr(e)}'")
self.failed.emit() self.failed.emit()
else: else:
self.checked.emit(update_info) self.checked.emit(update_info)

6
electrum/gui/stdio.py

@ -1,12 +1,14 @@
from decimal import Decimal from decimal import Decimal
import getpass import getpass
import datetime import datetime
import logging
from electrum import WalletStorage, Wallet from electrum import WalletStorage, Wallet
from electrum.util import format_satoshis, set_verbosity from electrum.util import format_satoshis
from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS
from electrum.transaction import TxOutput from electrum.transaction import TxOutput
from electrum.network import TxBroadcastError, BestEffortRequestFailed from electrum.network import TxBroadcastError, BestEffortRequestFailed
from electrum.logging import console_stderr_handler
_ = lambda x:x # i18n _ = lambda x:x # i18n
@ -30,7 +32,7 @@ class ElectrumGui:
self.done = 0 self.done = 0
self.last_balance = "" self.last_balance = ""
set_verbosity(False) console_stderr_handler.setLevel(logging.CRITICAL)
self.str_recipient = "" self.str_recipient = ""
self.str_description = "" self.str_description = ""

6
electrum/gui/text.py

@ -5,15 +5,17 @@ import datetime
import locale import locale
from decimal import Decimal from decimal import Decimal
import getpass import getpass
import logging
import electrum import electrum
from electrum.util import format_satoshis, set_verbosity from electrum.util import format_satoshis
from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS from electrum.bitcoin import is_address, COIN, TYPE_ADDRESS
from electrum.transaction import TxOutput from electrum.transaction import TxOutput
from electrum.wallet import Wallet from electrum.wallet import Wallet
from electrum.storage import WalletStorage from electrum.storage import WalletStorage
from electrum.network import NetworkParameters, TxBroadcastError, BestEffortRequestFailed from electrum.network import NetworkParameters, TxBroadcastError, BestEffortRequestFailed
from electrum.interface import deserialize_server from electrum.interface import deserialize_server
from electrum.logging import console_stderr_handler
_ = lambda x:x # i18n _ = lambda x:x # i18n
@ -52,7 +54,7 @@ class ElectrumGui:
self.set_cursor(0) self.set_cursor(0)
self.w = curses.newwin(10, 50, 5, 5) self.w = curses.newwin(10, 50, 5, 5)
set_verbosity(False) console_stderr_handler.setLevel(logging.CRITICAL)
self.tab = 0 self.tab = 0
self.pos = 0 self.pos = 0
self.popup_pos = 0 self.popup_pos = 0

51
electrum/interface.py

@ -37,7 +37,7 @@ from aiorpcx import RPCSession, Notification, NetAddress
from aiorpcx.curio import timeout_after, TaskTimeout from aiorpcx.curio import timeout_after, TaskTimeout
import certifi import certifi
from .util import PrintError, ignore_exceptions, log_exceptions, bfh, SilentTaskGroup from .util import ignore_exceptions, log_exceptions, bfh, SilentTaskGroup
from . import util from . import util
from . import x509 from . import x509
from . import pem from . import pem
@ -46,6 +46,7 @@ from . import blockchain
from .blockchain import Blockchain from .blockchain import Blockchain
from . import constants from . import constants
from .i18n import _ from .i18n import _
from .logging import Logger
if TYPE_CHECKING: if TYPE_CHECKING:
from .network import Network from .network import Network
@ -98,7 +99,7 @@ class NotificationSession(RPCSession):
else: else:
raise Exception(f'unexpected request. not a notification') raise Exception(f'unexpected request. not a notification')
except Exception as e: except Exception as e:
self.interface.print_error(f"error handling request {request}. exc: {repr(e)}") self.interface.logger.info(f"error handling request {request}. exc: {repr(e)}")
await self.close() await self.close()
async def send_request(self, *args, timeout=None, **kwargs): async def send_request(self, *args, timeout=None, **kwargs):
@ -148,7 +149,7 @@ class NotificationSession(RPCSession):
def maybe_log(self, msg: str) -> None: def maybe_log(self, msg: str) -> None:
if not self.interface: return if not self.interface: return
if self.interface.debug or self.interface.network.debug: if self.interface.debug or self.interface.network.debug:
self.interface.print_error(msg) self.interface.logger.debug(msg)
class GracefulDisconnect(Exception): pass class GracefulDisconnect(Exception): pass
@ -180,8 +181,7 @@ def serialize_server(host: str, port: Union[str, int], protocol: str) -> str:
return str(':'.join([host, str(port), protocol])) return str(':'.join([host, str(port), protocol]))
class Interface(PrintError): class Interface(Logger):
verbosity_filter = 'i'
def __init__(self, network: 'Network', server: str, proxy: Optional[dict]): def __init__(self, network: 'Network', server: str, proxy: Optional[dict]):
self.ready = asyncio.Future() self.ready = asyncio.Future()
@ -189,6 +189,7 @@ class Interface(PrintError):
self.server = server self.server = server
self.host, self.port, self.protocol = deserialize_server(self.server) self.host, self.port, self.protocol = deserialize_server(self.server)
self.port = int(self.port) self.port = int(self.port)
Logger.__init__(self)
assert network.config.path assert network.config.path
self.cert_path = os.path.join(network.config.path, 'certs', self.host) self.cert_path = os.path.join(network.config.path, 'certs', self.host)
self.blockchain = None self.blockchain = None
@ -209,7 +210,7 @@ class Interface(PrintError):
self.group = SilentTaskGroup() self.group = SilentTaskGroup()
def diagnostic_name(self): def diagnostic_name(self):
return self.host return f"{self.host}:{self.port}"
def _set_proxy(self, proxy: dict): def _set_proxy(self, proxy: dict):
if proxy: if proxy:
@ -263,18 +264,18 @@ class Interface(PrintError):
try: try:
b = pem.dePem(contents, 'CERTIFICATE') b = pem.dePem(contents, 'CERTIFICATE')
except SyntaxError as e: except SyntaxError as e:
self.print_error("error parsing already saved cert:", e) self.logger.info(f"error parsing already saved cert: {e}")
raise ErrorParsingSSLCert(e) from e raise ErrorParsingSSLCert(e) from e
try: try:
x = x509.X509(b) x = x509.X509(b)
except Exception as e: except Exception as e:
self.print_error("error parsing already saved cert:", e) self.logger.info(f"error parsing already saved cert: {e}")
raise ErrorParsingSSLCert(e) from e raise ErrorParsingSSLCert(e) from e
try: try:
x.check_date() x.check_date()
return True return True
except x509.CertificateError as e: except x509.CertificateError as e:
self.print_error("certificate has expired:", e) self.logger.info(f"certificate has expired: {e}")
os.unlink(self.cert_path) # delete pinned cert only in this case os.unlink(self.cert_path) # delete pinned cert only in this case
return False return False
@ -306,7 +307,7 @@ class Interface(PrintError):
try: try:
return await func(self, *args, **kwargs) return await func(self, *args, **kwargs)
except GracefulDisconnect as e: except GracefulDisconnect as e:
self.print_error("disconnecting gracefully. {}".format(repr(e))) self.logger.info(f"disconnecting gracefully. {repr(e)}")
finally: finally:
await self.network.connection_down(self) await self.network.connection_down(self)
self.got_disconnected.set_result(1) self.got_disconnected.set_result(1)
@ -321,12 +322,12 @@ class Interface(PrintError):
try: try:
ssl_context = await self._get_ssl_context() ssl_context = await self._get_ssl_context()
except (ErrorParsingSSLCert, ErrorGettingSSLCertFromServer) as e: except (ErrorParsingSSLCert, ErrorGettingSSLCertFromServer) as e:
self.print_error('disconnecting due to: {}'.format(repr(e))) self.logger.info(f'disconnecting due to: {repr(e)}')
return return
try: try:
await self.open_session(ssl_context) await self.open_session(ssl_context)
except (asyncio.CancelledError, OSError, aiorpcx.socks.SOCKSError) as e: except (asyncio.CancelledError, OSError, aiorpcx.socks.SOCKSError) as e:
self.print_error('disconnecting due to: {}'.format(repr(e))) self.logger.info(f'disconnecting due to: {repr(e)}')
return return
def mark_ready(self): def mark_ready(self):
@ -343,7 +344,7 @@ class Interface(PrintError):
self.blockchain = chain self.blockchain = chain
assert self.blockchain is not None assert self.blockchain is not None
self.print_error("set blockchain with height", self.blockchain.height()) self.logger.info(f"set blockchain with height {self.blockchain.height()}")
self.ready.set_result(1) self.ready.set_result(1)
@ -353,7 +354,7 @@ class Interface(PrintError):
for _ in range(10): for _ in range(10):
dercert = await self.get_certificate() dercert = await self.get_certificate()
if dercert: if dercert:
self.print_error("succeeded in getting cert") self.logger.info("succeeded in getting cert")
with open(self.cert_path, 'w') as f: with open(self.cert_path, 'w') as f:
cert = ssl.DER_cert_to_PEM_cert(dercert) cert = ssl.DER_cert_to_PEM_cert(dercert)
# workaround android bug # workaround android bug
@ -380,7 +381,7 @@ class Interface(PrintError):
return None return None
async def get_block_header(self, height, assert_mode): async def get_block_header(self, height, assert_mode):
self.print_error('requesting block header {} in mode {}'.format(height, assert_mode)) self.logger.info(f'requesting block header {height} in mode {assert_mode}')
# use lower timeout as we usually have network.bhi_lock here # use lower timeout as we usually have network.bhi_lock here
timeout = self.network.get_network_timeout_seconds(NetworkTimeout.Urgent) timeout = self.network.get_network_timeout_seconds(NetworkTimeout.Urgent)
res = await self.session.send_request('blockchain.block.header', [height], timeout=timeout) res = await self.session.send_request('blockchain.block.header', [height], timeout=timeout)
@ -390,7 +391,7 @@ class Interface(PrintError):
index = height // 2016 index = height // 2016
if can_return_early and index in self._requested_chunks: if can_return_early and index in self._requested_chunks:
return return
self.print_error("requesting chunk from height {}".format(height)) self.logger.info(f"requesting chunk from height {height}")
size = 2016 size = 2016
if tip is not None: if tip is not None:
size = min(size, tip - index * 2016 + 1) size = min(size, tip - index * 2016 + 1)
@ -425,7 +426,7 @@ class Interface(PrintError):
if not self.network.check_interface_against_healthy_spread_of_connected_servers(self): if not self.network.check_interface_against_healthy_spread_of_connected_servers(self):
raise GracefulDisconnect(f'too many connected servers already ' raise GracefulDisconnect(f'too many connected servers already '
f'in bucket {self.bucket_based_on_ipaddress()}') f'in bucket {self.bucket_based_on_ipaddress()}')
self.print_error("connection established. version: {}".format(ver)) self.logger.info(f"connection established. version: {ver}")
async with self.group as group: async with self.group as group:
await group.spawn(self.ping) await group.spawn(self.ping)
@ -472,7 +473,7 @@ class Interface(PrintError):
async with self.network.bhi_lock: async with self.network.bhi_lock:
if self.blockchain.height() >= height and self.blockchain.check_header(header): if self.blockchain.height() >= height and self.blockchain.check_header(header):
# another interface amended the blockchain # another interface amended the blockchain
self.print_error("skipping header", height) self.logger.info(f"skipping header {height}")
return return
_, height = await self.step(height, header) _, height = await self.step(height, header)
# in the simple case, height == self.tip+1 # in the simple case, height == self.tip+1
@ -518,13 +519,13 @@ class Interface(PrintError):
can_connect = blockchain.can_connect(header) if 'mock' not in header else header['mock']['connect'](height) can_connect = blockchain.can_connect(header) if 'mock' not in header else header['mock']['connect'](height)
if not can_connect: if not can_connect:
self.print_error("can't connect", height) self.logger.info(f"can't connect {height}")
height, header, bad, bad_header = await self._search_headers_backwards(height, header) height, header, bad, bad_header = await self._search_headers_backwards(height, header)
chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header) chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header)
can_connect = blockchain.can_connect(header) if 'mock' not in header else header['mock']['connect'](height) can_connect = blockchain.can_connect(header) if 'mock' not in header else header['mock']['connect'](height)
assert chain or can_connect assert chain or can_connect
if can_connect: if can_connect:
self.print_error("could connect", height) self.logger.info(f"could connect {height}")
height += 1 height += 1
if isinstance(can_connect, Blockchain): # not when mocking if isinstance(can_connect, Blockchain): # not when mocking
self.blockchain = can_connect self.blockchain = can_connect
@ -543,7 +544,7 @@ class Interface(PrintError):
while True: while True:
assert good < bad, (good, bad) assert good < bad, (good, bad)
height = (good + bad) // 2 height = (good + bad) // 2
self.print_error("binary step. good {}, bad {}, height {}".format(good, bad, height)) self.logger.info(f"binary step. good {good}, bad {bad}, height {height}")
header = await self.get_block_header(height, 'binary') header = await self.get_block_header(height, 'binary')
chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header) chain = blockchain.check_header(header) if 'mock' not in header else header['mock']['check'](header)
if chain: if chain:
@ -561,7 +562,7 @@ class Interface(PrintError):
raise Exception('unexpected bad header during binary: {}'.format(bad_header)) raise Exception('unexpected bad header during binary: {}'.format(bad_header))
_assert_header_does_not_check_against_any_chain(bad_header) _assert_header_does_not_check_against_any_chain(bad_header)
self.print_error("binary search exited. good {}, bad {}".format(good, bad)) self.logger.info(f"binary search exited. good {good}, bad {bad}")
return good, bad, bad_header return good, bad, bad_header
async def _resolve_potential_chain_fork_given_forkpoint(self, good, bad, bad_header): async def _resolve_potential_chain_fork_given_forkpoint(self, good, bad, bad_header):
@ -575,12 +576,12 @@ class Interface(PrintError):
assert bh >= good, (bh, good) assert bh >= good, (bh, good)
if bh == good: if bh == good:
height = good + 1 height = good + 1
self.print_error("catching up from {}".format(height)) self.logger.info(f"catching up from {height}")
return 'no_fork', height return 'no_fork', height
# this is a new fork we don't yet have # this is a new fork we don't yet have
height = bad + 1 height = bad + 1
self.print_error(f"new fork at bad height {bad}") self.logger.info(f"new fork at bad height {bad}")
forkfun = self.blockchain.fork if 'mock' not in bad_header else bad_header['mock']['fork'] forkfun = self.blockchain.fork if 'mock' not in bad_header else bad_header['mock']['fork']
b = forkfun(bad_header) # type: Blockchain b = forkfun(bad_header) # type: Blockchain
self.blockchain = b self.blockchain = b
@ -614,7 +615,7 @@ class Interface(PrintError):
height = self.tip - 2 * delta height = self.tip - 2 * delta
_assert_header_does_not_check_against_any_chain(bad_header) _assert_header_does_not_check_against_any_chain(bad_header)
self.print_error("exiting backward mode at", height) self.logger.info(f"exiting backward mode at {height}")
return height, header, bad, bad_header return height, header, bad, bad_header
@classmethod @classmethod

16
electrum/json_db.py

@ -31,9 +31,10 @@ from collections import defaultdict
from typing import Dict, Optional from typing import Dict, Optional
from . import util, bitcoin from . import util, bitcoin
from .util import PrintError, profiler, WalletFileException, multisig_type, TxMinedInfo from .util import profiler, WalletFileException, multisig_type, TxMinedInfo
from .keystore import bip44_derivation from .keystore import bip44_derivation
from .transaction import Transaction from .transaction import Transaction
from .logging import Logger
# seed_version is now used for the version of the wallet file # seed_version is now used for the version of the wallet file
@ -50,9 +51,10 @@ class JsonDBJsonEncoder(util.MyEncoder):
return super().default(obj) return super().default(obj)
class JsonDB(PrintError): class JsonDB(Logger):
def __init__(self, raw, *, manual_upgrades): def __init__(self, raw, *, manual_upgrades):
Logger.__init__(self)
self.lock = threading.RLock() self.lock = threading.RLock()
self.data = {} self.data = {}
self._modified = False self._modified = False
@ -98,7 +100,7 @@ class JsonDB(PrintError):
json.dumps(key, cls=JsonDBJsonEncoder) json.dumps(key, cls=JsonDBJsonEncoder)
json.dumps(value, cls=JsonDBJsonEncoder) json.dumps(value, cls=JsonDBJsonEncoder)
except: except:
self.print_error(f"json error: cannot save {repr(key)} ({repr(value)})") self.logger.info(f"json error: cannot save {repr(key)} ({repr(value)})")
return False return False
if value is not None: if value is not None:
if self.data.get(key) != value: if self.data.get(key) != value:
@ -137,7 +139,7 @@ class JsonDB(PrintError):
json.dumps(key) json.dumps(key)
json.dumps(value) json.dumps(value)
except: except:
self.print_error('Failed to convert label to json format', key) self.logger.info(f'Failed to convert label to json format: {key}')
continue continue
self.data[key] = value self.data[key] = value
if not isinstance(self.data, dict): if not isinstance(self.data, dict):
@ -198,7 +200,7 @@ class JsonDB(PrintError):
@profiler @profiler
def upgrade(self): def upgrade(self):
self.print_error('upgrading wallet format') self.logger.info('upgrading wallet format')
self._convert_imported() self._convert_imported()
self._convert_wallet_type() self._convert_wallet_type()
self._convert_account() self._convert_account()
@ -755,14 +757,14 @@ class JsonDB(PrintError):
# remove unreferenced tx # remove unreferenced tx
for tx_hash in list(self.transactions.keys()): for tx_hash in list(self.transactions.keys()):
if not self.get_txi(tx_hash) and not self.get_txo(tx_hash): if not self.get_txi(tx_hash) and not self.get_txo(tx_hash):
self.print_error("removing unreferenced tx", tx_hash) self.logger.info(f"removing unreferenced tx: {tx_hash}")
self.transactions.pop(tx_hash) self.transactions.pop(tx_hash)
# remove unreferenced outpoints # remove unreferenced outpoints
for prevout_hash in self.spent_outpoints.keys(): for prevout_hash in self.spent_outpoints.keys():
d = self.spent_outpoints[prevout_hash] d = self.spent_outpoints[prevout_hash]
for prevout_n, spending_txid in list(d.items()): for prevout_n, spending_txid in list(d.items()):
if spending_txid not in self.transactions: if spending_txid not in self.transactions:
self.print_error("removing unreferenced spent outpoint") self.logger.info("removing unreferenced spent outpoint")
d.pop(prevout_n) d.pop(prevout_n)
@modifier @modifier

8
electrum/jsonrpc.py

@ -29,6 +29,7 @@ import time
from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer, SimpleJSONRPCRequestHandler from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer, SimpleJSONRPCRequestHandler
from . import util from . import util
from .logging import Logger
class RPCAuthCredentialsInvalid(Exception): class RPCAuthCredentialsInvalid(Exception):
@ -47,10 +48,10 @@ class RPCAuthUnsupportedType(Exception):
# based on http://acooke.org/cute/BasicHTTPA0.html by andrew cooke # based on http://acooke.org/cute/BasicHTTPA0.html by andrew cooke
class VerifyingJSONRPCServer(SimpleJSONRPCServer): class VerifyingJSONRPCServer(SimpleJSONRPCServer, Logger):
def __init__(self, *args, rpc_user, rpc_password, **kargs): def __init__(self, *args, rpc_user, rpc_password, **kargs):
Logger.__init__(self)
self.rpc_user = rpc_user self.rpc_user = rpc_user
self.rpc_password = rpc_password self.rpc_password = rpc_password
@ -69,8 +70,7 @@ class VerifyingJSONRPCServer(SimpleJSONRPCServer):
RPCAuthUnsupportedType) as e: RPCAuthUnsupportedType) as e:
myself.send_error(401, str(e)) myself.send_error(401, str(e))
except BaseException as e: except BaseException as e:
import traceback, sys self.logger.exception('')
traceback.print_exc(file=sys.stderr)
myself.send_error(500, str(e)) myself.send_error(500, str(e))
return False return False

15
electrum/keystore.py

@ -36,13 +36,17 @@ from .bip32 import (convert_bip32_path_to_list_of_uint32, BIP32_PRIME,
from .ecc import string_to_number, number_to_string from .ecc import string_to_number, number_to_string
from .crypto import (pw_decode, pw_encode, sha256, sha256d, PW_HASH_VERSION_LATEST, from .crypto import (pw_decode, pw_encode, sha256, sha256d, PW_HASH_VERSION_LATEST,
SUPPORTED_PW_HASH_VERSIONS, UnsupportedPasswordHashVersion) SUPPORTED_PW_HASH_VERSIONS, UnsupportedPasswordHashVersion)
from .util import (PrintError, InvalidPassword, WalletFileException, from .util import (InvalidPassword, WalletFileException,
BitcoinException, bh2u, bfh, print_error, inv_dict) BitcoinException, bh2u, bfh, inv_dict)
from .mnemonic import Mnemonic, load_wordlist, seed_type, is_seed from .mnemonic import Mnemonic, load_wordlist, seed_type, is_seed
from .plugin import run_hook from .plugin import run_hook
from .logging import Logger
class KeyStore(PrintError): class KeyStore(Logger):
def __init__(self):
Logger.__init__(self)
def has_seed(self): def has_seed(self):
return False return False
@ -463,7 +467,6 @@ class Old_KeyStore(Deterministic_KeyStore):
master_private_key = ecc.ECPrivkey.from_secret_scalar(secexp) master_private_key = ecc.ECPrivkey.from_secret_scalar(secexp)
master_public_key = master_private_key.get_public_key_bytes(compressed=False)[1:] master_public_key = master_private_key.get_public_key_bytes(compressed=False)[1:]
if master_public_key != bfh(self.mpk): if master_public_key != bfh(self.mpk):
print_error('invalid password (mpk)', self.mpk, bh2u(master_public_key))
raise InvalidPassword() raise InvalidPassword()
def check_password(self, password): def check_password(self, password):
@ -554,12 +557,12 @@ class Hardware_KeyStore(KeyStore, Xpub):
def unpaired(self): def unpaired(self):
'''A device paired with the wallet was disconnected. This can be '''A device paired with the wallet was disconnected. This can be
called in any thread context.''' called in any thread context.'''
self.print_error("unpaired") self.logger.info("unpaired")
def paired(self): def paired(self):
'''A device paired with the wallet was (re-)connected. This can be '''A device paired with the wallet was (re-)connected. This can be
called in any thread context.''' called in any thread context.'''
self.print_error("paired") self.logger.info("paired")
def can_export(self): def can_export(self):
return False return False

164
electrum/logging.py

@ -0,0 +1,164 @@
# Copyright (C) 2019 The Electrum developers
# Distributed under the MIT software license, see the accompanying
# file LICENCE or http://www.opensource.org/licenses/mit-license.php
import logging
import datetime
import sys
import pathlib
import os
import platform
from typing import Optional
class LogFormatterForFiles(logging.Formatter):
def formatTime(self, record, datefmt=None):
# timestamps follow ISO 8601 UTC
date = datetime.datetime.fromtimestamp(record.created).astimezone(datetime.timezone.utc)
if not datefmt:
datefmt = "%Y%m%dT%H%M%S.%fZ"
return date.strftime(datefmt)
file_formatter = LogFormatterForFiles(fmt="%(asctime)22s | %(levelname)8s | %(name)s | %(message)s")
class LogFormatterForConsole(logging.Formatter):
def format(self, record):
# strip the main module name from the logger name
if record.name.startswith("electrum."):
record.name = record.name[9:]
# manual map to shorten common module names
record.name = record.name.replace("interface.Interface", "interface", 1)
record.name = record.name.replace("network.Network", "network", 1)
record.name = record.name.replace("synchronizer.Synchronizer", "synchronizer", 1)
record.name = record.name.replace("verifier.SPV", "verifier", 1)
record.name = record.name.replace("gui.qt.main_window.ElectrumWindow", "gui.qt.main_window", 1)
return super().format(record)
# try to make console log lines short... no timestamp, short levelname, no "electrum."
console_formatter = LogFormatterForConsole(fmt="%(levelname).1s | %(name)s | %(message)s")
# enable logs universally (including for other libraries)
root_logger = logging.getLogger()
root_logger.setLevel(logging.WARNING)
# log to stderr; by default only WARNING and higher
console_stderr_handler = logging.StreamHandler(sys.stderr)
console_stderr_handler.setFormatter(console_formatter)
console_stderr_handler.setLevel(logging.WARNING)
root_logger.addHandler(console_stderr_handler)
# creates a logger specifically for electrum library
electrum_logger = logging.getLogger("electrum")
electrum_logger.setLevel(logging.DEBUG)
def _delete_old_logs(path, keep=10):
files = sorted(list(pathlib.Path(path).glob("electrum_log_*.log")), reverse=True)
for f in files[keep:]:
os.remove(str(f))
_logfile_path = None
def _configure_file_logging(log_directory: pathlib.Path):
global _logfile_path
assert _logfile_path is None, 'file logging already initialized'
log_directory.mkdir(exist_ok=True)
_delete_old_logs(log_directory)
timestamp = datetime.datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
PID = os.getpid()
_logfile_path = log_directory / f"electrum_log_{timestamp}_{PID}.log"
file_handler = logging.FileHandler(_logfile_path)
file_handler.setFormatter(file_formatter)
file_handler.setLevel(logging.DEBUG)
root_logger.addHandler(file_handler)
def _configure_verbosity(config):
verbosity = config.get('verbosity')
if not verbosity:
return
console_stderr_handler.setLevel(logging.DEBUG)
if verbosity == '*' or not isinstance(verbosity, str):
return
# example verbosity:
# debug,network=error,interface=error // effectively blacklists network and interface
# warning,network=debug,interface=debug // effectively whitelists network and interface
filters = verbosity.split(',')
for filt in filters:
if not filt: continue
items = filt.split('=')
if len(items) == 1:
level = items[0]
electrum_logger.setLevel(level.upper())
elif len(items) == 2:
logger_name, level = items
logger = get_logger(logger_name)
logger.setLevel(level.upper())
else:
raise Exception(f"invalid log filter: {filt}")
# --- External API
def get_logger(name: str) -> logging.Logger:
if name.startswith("electrum."):
name = name[9:]
return electrum_logger.getChild(name)
_logger = get_logger(__name__)
_logger.setLevel(logging.INFO)
class Logger:
def __init__(self):
self.logger = self.__get_logger_for_obj()
def __get_logger_for_obj(self) -> logging.Logger:
cls = self.__class__
if cls.__module__:
name = f"{cls.__module__}.{cls.__name__}"
else:
name = cls.__name__
try:
diag_name = self.diagnostic_name()
except Exception as e:
raise Exception("diagnostic name not yet available?") from e
if diag_name:
name += f".[{diag_name}]"
return get_logger(name)
def diagnostic_name(self):
return ''
def configure_logging(config):
_configure_verbosity(config)
is_android = 'ANDROID_DATA' in os.environ
if is_android or config.get('disablefilelogging'):
pass # disable file logging
else:
log_directory = pathlib.Path(config.path) / "logs"
_configure_file_logging(log_directory)
# if using kivy, avoid kivy's own logs to get printed twice
logging.getLogger('kivy').propagate = False
from . import ELECTRUM_VERSION
_logger.info(f"Electrum version: {ELECTRUM_VERSION} - https://electrum.org - https://github.com/spesmilo/electrum")
_logger.info(f"Python version: {sys.version}. On platform: {platform.platform()}")
_logger.info(f"Logging to file: {str(_logfile_path)}")
def get_logfile_path() -> Optional[pathlib.Path]:
return _logfile_path

15
electrum/mnemonic.py

@ -30,9 +30,11 @@ import string
import ecdsa import ecdsa
from .util import print_error, resource_path, bfh, bh2u from .util import resource_path, bfh, bh2u
from .crypto import hmac_oneshot from .crypto import hmac_oneshot
from . import version from . import version
from .logging import Logger
# http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/e_asia.html # http://www.asahi-net.or.jp/~ax2s-kmtn/ref/unicode/e_asia.html
CJK_INTERVALS = [ CJK_INTERVALS = [
@ -114,16 +116,17 @@ filenames = {
# FIXME every time we instantiate this class, we read the wordlist from disk # FIXME every time we instantiate this class, we read the wordlist from disk
# and store a new copy of it in memory # and store a new copy of it in memory
class Mnemonic(object): class Mnemonic(Logger):
# Seed derivation does not follow BIP39 # Seed derivation does not follow BIP39
# Mnemonic phrase uses a hash based checksum, instead of a wordlist-dependent checksum # Mnemonic phrase uses a hash based checksum, instead of a wordlist-dependent checksum
def __init__(self, lang=None): def __init__(self, lang=None):
Logger.__init__(self)
lang = lang or 'en' lang = lang or 'en'
print_error('language', lang) self.logger.info(f'language {lang}')
filename = filenames.get(lang[0:2], 'english.txt') filename = filenames.get(lang[0:2], 'english.txt')
self.wordlist = load_wordlist(filename) self.wordlist = load_wordlist(filename)
print_error("wordlist has %d words"%len(self.wordlist)) self.logger.info(f"wordlist has {len(self.wordlist)} words")
@classmethod @classmethod
def mnemonic_to_seed(self, mnemonic, passphrase) -> bytes: def mnemonic_to_seed(self, mnemonic, passphrase) -> bytes:
@ -163,7 +166,7 @@ class Mnemonic(object):
bpw = math.log(len(self.wordlist), 2) bpw = math.log(len(self.wordlist), 2)
# rounding # rounding
n = int(math.ceil(num_bits/bpw) * bpw) n = int(math.ceil(num_bits/bpw) * bpw)
print_error("make_seed. prefix: '%s'"%prefix, "entropy: %d bits"%n) self.logger.info(f"make_seed. prefix: '{prefix}', entropy: {n} bits")
entropy = 1 entropy = 1
while entropy < pow(2, n - bpw): while entropy < pow(2, n - bpw):
# try again if seed would not contain enough words # try again if seed would not contain enough words
@ -179,7 +182,7 @@ class Mnemonic(object):
continue continue
if is_new_seed(seed, prefix): if is_new_seed(seed, prefix):
break break
print_error('%d words'%len(seed.split())) self.logger.info(f'{len(seed.split())} words')
return seed return seed

65
electrum/network.py

@ -42,7 +42,7 @@ from aiorpcx import TaskGroup
from aiohttp import ClientResponse from aiohttp import ClientResponse
from . import util from . import util
from .util import (PrintError, print_error, log_exceptions, ignore_exceptions, from .util import (log_exceptions, ignore_exceptions,
bfh, SilentTaskGroup, make_aiohttp_session, send_exception_to_crash_reporter, bfh, SilentTaskGroup, make_aiohttp_session, send_exception_to_crash_reporter,
is_hash256_str, is_non_negative_integer) is_hash256_str, is_non_negative_integer)
@ -56,6 +56,11 @@ from .interface import (Interface, serialize_server, deserialize_server,
from .version import PROTOCOL_VERSION from .version import PROTOCOL_VERSION
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
from .i18n import _ from .i18n import _
from .logging import get_logger, Logger
_logger = get_logger(__name__)
NODES_RETRY_INTERVAL = 60 NODES_RETRY_INTERVAL = 60
SERVER_RETRY_INTERVAL = 10 SERVER_RETRY_INTERVAL = 10
@ -214,16 +219,17 @@ class UntrustedServerReturnedError(Exception):
INSTANCE = None INSTANCE = None
class Network(PrintError): class Network(Logger):
"""The Network class manages a set of connections to remote electrum """The Network class manages a set of connections to remote electrum
servers, each connected socket is handled by an Interface() object. servers, each connected socket is handled by an Interface() object.
""" """
verbosity_filter = 'n'
def __init__(self, config: SimpleConfig=None): def __init__(self, config: SimpleConfig=None):
global INSTANCE global INSTANCE
INSTANCE = self INSTANCE = self
Logger.__init__(self)
self.asyncio_loop = asyncio.get_event_loop() self.asyncio_loop = asyncio.get_event_loop()
assert self.asyncio_loop.is_running(), "event loop not running" assert self.asyncio_loop.is_running(), "event loop not running"
self._loop_thread = None # type: threading.Thread # set by caller; only used for sanity checks self._loop_thread = None # type: threading.Thread # set by caller; only used for sanity checks
@ -232,7 +238,7 @@ class Network(PrintError):
config = {} # Do not use mutables as default values! config = {} # Do not use mutables as default values!
self.config = SimpleConfig(config) if isinstance(config, dict) else config # type: SimpleConfig self.config = SimpleConfig(config) if isinstance(config, dict) else config # type: SimpleConfig
blockchain.read_blockchains(self.config) blockchain.read_blockchains(self.config)
self.print_error("blockchains", list(map(lambda b: b.forkpoint, blockchain.blockchains.values()))) 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: Optional[Dict] self._blockchain_preferred_block = self.config.get('blockchain_preferred_block', None) # type: Optional[Dict]
self._blockchain = blockchain.get_best_chain() self._blockchain = blockchain.get_best_chain()
# Server for addresses and transactions # Server for addresses and transactions
@ -242,7 +248,7 @@ class Network(PrintError):
try: try:
deserialize_server(self.default_server) deserialize_server(self.default_server)
except: except:
self.print_error('Warning: failed to parse server-string; falling back to random.') self.logger.warning('failed to parse server-string; falling back to random.')
self.default_server = None self.default_server = None
if not self.default_server: if not self.default_server:
self.default_server = pick_random_server() self.default_server = pick_random_server()
@ -351,12 +357,12 @@ class Network(PrintError):
async def _server_is_lagging(self): async def _server_is_lagging(self):
sh = self.get_server_height() sh = self.get_server_height()
if not sh: if not sh:
self.print_error('no height for main interface') self.logger.info('no height for main interface')
return True return True
lh = self.get_local_height() lh = self.get_local_height()
result = (lh - sh) > 1 result = (lh - sh) > 1
if result: if result:
self.print_error(f'{self.default_server} is lagging ({sh} vs {lh})') self.logger.info(f'{self.default_server} is lagging ({sh} vs {lh})')
return result return result
def _set_status(self, status): def _set_status(self, status):
@ -381,7 +387,7 @@ class Network(PrintError):
addr = await session.send_request('server.donation_address') addr = await session.send_request('server.donation_address')
if not bitcoin.is_address(addr): if not bitcoin.is_address(addr):
if addr: # ignore empty string if addr: # ignore empty string
self.print_error(f"invalid donation address from server: {repr(addr)}") self.logger.info(f"invalid donation address from server: {repr(addr)}")
addr = '' addr = ''
self.donation_address = addr self.donation_address = addr
async def get_server_peers(): async def get_server_peers():
@ -416,7 +422,7 @@ class Network(PrintError):
for i in FEE_ETA_TARGETS: for i in FEE_ETA_TARGETS:
fee_tasks.append((i, await group.spawn(session.send_request('blockchain.estimatefee', [i])))) fee_tasks.append((i, await group.spawn(session.send_request('blockchain.estimatefee', [i]))))
self.config.mempool_fees = histogram = histogram_task.result() self.config.mempool_fees = histogram = histogram_task.result()
self.print_error(f'fee_histogram {histogram}') self.logger.info(f'fee_histogram {histogram}')
self.notify('fee_histogram') self.notify('fee_histogram')
fee_estimates_eta = {} fee_estimates_eta = {}
for nblock_target, task in fee_tasks: for nblock_target, task in fee_tasks:
@ -424,7 +430,7 @@ class Network(PrintError):
fee_estimates_eta[nblock_target] = fee fee_estimates_eta[nblock_target] = fee
if fee < 0: continue if fee < 0: continue
self.config.update_fee_estimates(nblock_target, fee) self.config.update_fee_estimates(nblock_target, fee)
self.print_error(f'fee_estimates {fee_estimates_eta}') self.logger.info(f'fee_estimates {fee_estimates_eta}')
self.notify('fee') self.notify('fee')
def get_status_value(self, key): def get_status_value(self, key):
@ -490,7 +496,7 @@ class Network(PrintError):
def _start_interface(self, server: str): def _start_interface(self, server: str):
if server not in self.interfaces and server not in self.connecting: if server not in self.interfaces and server not in self.connecting:
if server == self.default_server: if server == self.default_server:
self.print_error(f"connecting to {server} as new interface") self.logger.info(f"connecting to {server} as new interface")
self._set_status('connecting') self._set_status('connecting')
self.connecting.add(server) self.connecting.add(server)
self.server_queue.put(server) self.server_queue.put(server)
@ -509,7 +515,7 @@ class Network(PrintError):
if not hasattr(socket, "_getaddrinfo"): if not hasattr(socket, "_getaddrinfo"):
socket._getaddrinfo = socket.getaddrinfo socket._getaddrinfo = socket.getaddrinfo
if proxy: if proxy:
self.print_error('setting proxy', proxy) self.logger.info(f'setting proxy {proxy}')
# prevent dns leaks, see http://stackoverflow.com/questions/13184205/dns-over-proxy # prevent dns leaks, see http://stackoverflow.com/questions/13184205/dns-over-proxy
socket.getaddrinfo = lambda *args: [(socket.AF_INET, socket.SOCK_STREAM, 6, '', (args[0], args[1]))] socket.getaddrinfo = lambda *args: [(socket.AF_INET, socket.SOCK_STREAM, 6, '', (args[0], args[1]))]
else: else:
@ -542,7 +548,7 @@ class Network(PrintError):
except dns.exception.DNSException as e: except dns.exception.DNSException as e:
pass pass
except BaseException as e: except BaseException as e:
print_error(f'dnspython failed to resolve dns (AAAA) with error: {e}') _logger.info(f'dnspython failed to resolve dns (AAAA) with error: {e}')
# try IPv4 # try IPv4
try: try:
answers = dns.resolver.query(host, dns.rdatatype.A) answers = dns.resolver.query(host, dns.rdatatype.A)
@ -554,7 +560,7 @@ class Network(PrintError):
raise socket.gaierror(11001, 'getaddrinfo failed') from e raise socket.gaierror(11001, 'getaddrinfo failed') from e
except BaseException as e: except BaseException as e:
# Possibly internal error in dnspython :( see #4483 # Possibly internal error in dnspython :( see #4483
print_error(f'dnspython failed to resolve dns (A) with error: {e}') _logger.info(f'dnspython failed to resolve dns (A) with error: {e}')
if addrs: if addrs:
return addrs return addrs
# Fall back to original socket.getaddrinfo to resolve dns. # Fall back to original socket.getaddrinfo to resolve dns.
@ -641,24 +647,24 @@ class Network(PrintError):
filtered = list(filter(lambda iface: iface.blockchain.check_hash(pref_height, pref_hash), filtered = list(filter(lambda iface: iface.blockchain.check_hash(pref_height, pref_hash),
interfaces)) interfaces))
if filtered: if filtered:
self.print_error("switching to preferred fork") self.logger.info("switching to preferred fork")
chosen_iface = random.choice(filtered) chosen_iface = random.choice(filtered)
await self.switch_to_interface(chosen_iface.server) await self.switch_to_interface(chosen_iface.server)
return return
else: else:
self.print_error("tried to switch to preferred fork but no interfaces are on it") self.logger.info("tried to switch to preferred fork but no interfaces are on it")
# try to switch to best chain # try to switch to best chain
if self.blockchain().parent is None: if self.blockchain().parent is None:
return # already on best chain return # already on best chain
filtered = list(filter(lambda iface: iface.blockchain.parent is None, filtered = list(filter(lambda iface: iface.blockchain.parent is None,
interfaces)) interfaces))
if filtered: if filtered:
self.print_error("switching to best chain") self.logger.info("switching to best chain")
chosen_iface = random.choice(filtered) chosen_iface = random.choice(filtered)
await self.switch_to_interface(chosen_iface.server) await self.switch_to_interface(chosen_iface.server)
else: else:
# FIXME switch to best available? # FIXME switch to best available?
self.print_error("tried to switch to best chain but no interfaces are on it") self.logger.info("tried to switch to best chain but no interfaces are on it")
async def switch_to_interface(self, server: str): async def switch_to_interface(self, server: str):
"""Switch to server as our main interface. If no connection exists, """Switch to server as our main interface. If no connection exists,
@ -685,7 +691,7 @@ class Network(PrintError):
i = self.interfaces[server] i = self.interfaces[server]
if old_interface != i: if old_interface != i:
self.print_error("switching to", server) self.logger.info(f"switching to {server}")
blockchain_updated = i.blockchain != self.blockchain() blockchain_updated = i.blockchain != self.blockchain()
self.interface = i self.interface = i
await i.group.spawn(self._request_server_info(i)) await i.group.spawn(self._request_server_info(i))
@ -739,8 +745,7 @@ class Network(PrintError):
try: try:
await asyncio.wait_for(interface.ready, timeout) await asyncio.wait_for(interface.ready, timeout)
except BaseException as e: except BaseException as e:
#traceback.print_exc() self.logger.info(f"couldn't launch iface {server} -- {repr(e)}")
self.print_error(f"couldn't launch iface {server} -- {repr(e)}")
await interface.close() await interface.close()
return return
else: else:
@ -854,14 +859,14 @@ class Network(PrintError):
except (RequestTimedOut, asyncio.CancelledError, asyncio.TimeoutError): except (RequestTimedOut, asyncio.CancelledError, asyncio.TimeoutError):
raise # pass-through raise # pass-through
except aiorpcx.jsonrpc.CodeMessageError as e: except aiorpcx.jsonrpc.CodeMessageError as e:
self.print_error(f"broadcast_transaction error: {repr(e)}") self.logger.info(f"broadcast_transaction error: {repr(e)}")
raise TxBroadcastServerReturnedError(self.sanitize_tx_broadcast_response(e.message)) from e raise TxBroadcastServerReturnedError(self.sanitize_tx_broadcast_response(e.message)) from e
except BaseException as e: # intentional BaseException for sanity! except BaseException as e: # intentional BaseException for sanity!
self.print_error(f"broadcast_transaction error2: {repr(e)}") self.logger.info(f"broadcast_transaction error2: {repr(e)}")
send_exception_to_crash_reporter(e) send_exception_to_crash_reporter(e)
raise TxBroadcastUnknownError() from e raise TxBroadcastUnknownError() from e
if out != tx.txid(): if out != tx.txid():
self.print_error(f"unexpected txid for broadcast_transaction: {out} != {tx.txid()}") self.logger.info(f"unexpected txid for broadcast_transaction: {out} != {tx.txid()}")
raise TxBroadcastHashMismatch(_("Server returned unexpected transaction ID.")) raise TxBroadcastHashMismatch(_("Server returned unexpected transaction ID."))
@staticmethod @staticmethod
@ -1103,7 +1108,7 @@ class Network(PrintError):
self.main_taskgroup = main_taskgroup = SilentTaskGroup() self.main_taskgroup = main_taskgroup = SilentTaskGroup()
assert not self.interface and not self.interfaces assert not self.interface and not self.interfaces
assert not self.connecting and not self.server_queue assert not self.connecting and not self.server_queue
self.print_error('starting network') self.logger.info('starting network')
self.disconnected_servers = set([]) self.disconnected_servers = set([])
self.protocol = deserialize_server(self.default_server)[2] self.protocol = deserialize_server(self.default_server)[2]
self.server_queue = queue.Queue() self.server_queue = queue.Queue()
@ -1120,7 +1125,7 @@ class Network(PrintError):
await group.spawn(self._maintain_sessions()) await group.spawn(self._maintain_sessions())
[await group.spawn(job) for job in self._jobs] [await group.spawn(job) for job in self._jobs]
except Exception as e: except Exception as e:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
raise e raise e
asyncio.run_coroutine_threadsafe(main(), self.asyncio_loop) asyncio.run_coroutine_threadsafe(main(), self.asyncio_loop)
@ -1132,11 +1137,11 @@ class Network(PrintError):
@log_exceptions @log_exceptions
async def _stop(self, full_shutdown=False): async def _stop(self, full_shutdown=False):
self.print_error("stopping network") self.logger.info("stopping network")
try: try:
await asyncio.wait_for(self.main_taskgroup.cancel_remaining(), timeout=2) await asyncio.wait_for(self.main_taskgroup.cancel_remaining(), timeout=2)
except (asyncio.TimeoutError, asyncio.CancelledError) as e: except (asyncio.TimeoutError, asyncio.CancelledError) as e:
self.print_error(f"exc during main_taskgroup cancellation: {repr(e)}") self.logger.info(f"exc during main_taskgroup cancellation: {repr(e)}")
self.main_taskgroup = None # type: TaskGroup self.main_taskgroup = None # type: TaskGroup
self.interface = None # type: Interface self.interface = None # type: Interface
self.interfaces = {} # type: Dict[str, Interface] self.interfaces = {} # type: Dict[str, Interface]
@ -1179,7 +1184,7 @@ class Network(PrintError):
# FIXME this should try to honour "healthy spread of connected servers" # FIXME this should try to honour "healthy spread of connected servers"
self._start_random_interface() self._start_random_interface()
if now - self.nodes_retry_time > NODES_RETRY_INTERVAL: if now - self.nodes_retry_time > NODES_RETRY_INTERVAL:
self.print_error('network: retrying connections') self.logger.info('network: retrying connections')
self.disconnected_servers = set([]) self.disconnected_servers = set([])
self.nodes_retry_time = now self.nodes_retry_time = now
async def maintain_healthy_spread_of_connected_servers(): async def maintain_healthy_spread_of_connected_servers():
@ -1187,7 +1192,7 @@ class Network(PrintError):
random.shuffle(interfaces) random.shuffle(interfaces)
for iface in interfaces: for iface in interfaces:
if not self.check_interface_against_healthy_spread_of_connected_servers(iface): if not self.check_interface_against_healthy_spread_of_connected_servers(iface):
self.print_error(f"disconnecting from {iface.server}. too many connected " self.logger.info(f"disconnecting from {iface.server}. too many connected "
f"servers already in bucket {iface.bucket_based_on_ipaddress()}") f"servers already in bucket {iface.bucket_based_on_ipaddress()}")
await self._close_interface(iface) await self._close_interface(iface)
async def maintain_main_interface(): async def maintain_main_interface():

15
electrum/paymentrequest.py

@ -39,11 +39,15 @@ except ImportError:
sys.exit("Error: could not find paymentrequest_pb2.py. Create it with 'protoc --proto_path=electrum/ --python_out=electrum/ electrum/paymentrequest.proto'") sys.exit("Error: could not find paymentrequest_pb2.py. Create it with 'protoc --proto_path=electrum/ --python_out=electrum/ electrum/paymentrequest.proto'")
from . import bitcoin, ecc, util, transaction, x509, rsakey from . import bitcoin, ecc, util, transaction, x509, rsakey
from .util import print_error, bh2u, bfh, export_meta, import_meta, make_aiohttp_session from .util import bh2u, bfh, export_meta, import_meta, make_aiohttp_session
from .crypto import sha256 from .crypto import sha256
from .bitcoin import TYPE_ADDRESS from .bitcoin import TYPE_ADDRESS
from .transaction import TxOutput from .transaction import TxOutput
from .network import Network from .network import Network
from .logging import get_logger, Logger
_logger = get_logger(__name__)
REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', 'User-Agent': 'Electrum'} REQUEST_HEADERS = {'Accept': 'application/bitcoin-paymentrequest', 'User-Agent': 'Electrum'}
@ -86,7 +90,7 @@ async def get_payment_request(url: str) -> 'PaymentRequest':
else: else:
data = resp_content data = resp_content
data_len = len(data) if data is not None else None data_len = len(data) if data is not None else None
print_error('fetched payment request', url, data_len) _logger.info(f'fetched payment request {url} {data_len}')
except aiohttp.ClientError as e: except aiohttp.ClientError as e:
error = f"Error while contacting payment URL:\n{repr(e)}" error = f"Error while contacting payment URL:\n{repr(e)}"
if isinstance(e, aiohttp.ClientResponseError) and e.status == 400 and resp_content: if isinstance(e, aiohttp.ClientResponseError) and e.status == 400 and resp_content:
@ -180,7 +184,7 @@ class PaymentRequest:
try: try:
x, ca = verify_cert_chain(cert.certificate) x, ca = verify_cert_chain(cert.certificate)
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stderr) _logger.exception('')
self.error = str(e) self.error = str(e)
return False return False
# get requestor name # get requestor name
@ -454,9 +458,10 @@ def make_request(config, req):
class InvoiceStore(object): class InvoiceStore(Logger):
def __init__(self, storage): def __init__(self, storage):
Logger.__init__(self)
self.storage = storage self.storage = storage
self.invoices = {} self.invoices = {}
self.paid = {} self.paid = {}
@ -511,7 +516,7 @@ class InvoiceStore(object):
def get_status(self, key): def get_status(self, key):
pr = self.get(key) pr = self.get(key)
if pr is None: if pr is None:
print_error("[InvoiceStore] get_status() can't find pr for", key) self.logger.info(f"get_status() can't find pr for {key}")
return return
if pr.tx is not None: if pr.tx is not None:
return PR_PAID return PR_PAID

51
electrum/plugin.py

@ -22,8 +22,6 @@
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN # ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
import traceback
import sys
import os import os
import pkgutil import pkgutil
import importlib.util import importlib.util
@ -32,23 +30,23 @@ import threading
from typing import NamedTuple, Any, Union, TYPE_CHECKING, Optional from typing import NamedTuple, Any, Union, TYPE_CHECKING, Optional
from .i18n import _ from .i18n import _
from .util import (profiler, PrintError, DaemonThread, UserCancelled, from .util import (profiler, DaemonThread, UserCancelled, ThreadJob)
ThreadJob, print_error, UserFacingException)
from . import bip32 from . import bip32
from . import plugins from . import plugins
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
from .logging import get_logger, Logger
if TYPE_CHECKING: if TYPE_CHECKING:
from .plugins.hw_wallet import HW_PluginBase from .plugins.hw_wallet import HW_PluginBase
_logger = get_logger(__name__)
plugin_loaders = {} plugin_loaders = {}
hook_names = set() hook_names = set()
hooks = {} hooks = {}
class Plugins(DaemonThread): class Plugins(DaemonThread):
verbosity_filter = 'p'
@profiler @profiler
def __init__(self, config: SimpleConfig, gui_name): def __init__(self, config: SimpleConfig, gui_name):
@ -91,8 +89,7 @@ class Plugins(DaemonThread):
try: try:
self.load_plugin(name) self.load_plugin(name)
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stdout) self.logger.exception(f"cannot initialize plugin {name}: {e}")
self.print_error("cannot initialize plugin %s:" % name, str(e))
def get(self, name): def get(self, name):
return self.plugins.get(name) return self.plugins.get(name)
@ -116,7 +113,7 @@ class Plugins(DaemonThread):
raise Exception(f"Error loading {name} plugin: {repr(e)}") from e raise Exception(f"Error loading {name} plugin: {repr(e)}") from e
self.add_jobs(plugin.thread_jobs()) self.add_jobs(plugin.thread_jobs())
self.plugins[name] = plugin self.plugins[name] = plugin
self.print_error("loaded", name) self.logger.info(f"loaded {name}")
return plugin return plugin
def close_plugin(self, plugin): def close_plugin(self, plugin):
@ -136,7 +133,7 @@ class Plugins(DaemonThread):
return return
self.plugins.pop(name) self.plugins.pop(name)
p.close() p.close()
self.print_error("closed", name) self.logger.info(f"closed {name}")
def toggle(self, name): def toggle(self, name):
p = self.get(name) p = self.get(name)
@ -151,7 +148,7 @@ class Plugins(DaemonThread):
try: try:
__import__(dep) __import__(dep)
except ImportError as e: except ImportError as e:
self.print_error('Plugin', name, 'unavailable:', repr(e)) self.logger.warning(f'Plugin {name} unavailable: {repr(e)}')
return False return False
requires = d.get('requires_wallet_type', []) requires = d.get('requires_wallet_type', [])
return not requires or w.wallet_type in requires return not requires or w.wallet_type in requires
@ -168,8 +165,7 @@ class Plugins(DaemonThread):
plugin=p, plugin=p,
exception=None)) exception=None))
except Exception as e: except Exception as e:
traceback.print_exc() self.logger.exception(f"cannot load plugin for: {name}")
self.print_error("cannot load plugin for:", name)
out.append(HardwarePluginToScan(name=name, out.append(HardwarePluginToScan(name=name,
description=details[2], description=details[2],
plugin=None, plugin=None,
@ -178,7 +174,7 @@ class Plugins(DaemonThread):
def register_wallet_type(self, name, gui_good, wallet_type): def register_wallet_type(self, name, gui_good, wallet_type):
from .wallet import register_wallet_type, register_constructor from .wallet import register_wallet_type, register_constructor
self.print_error("registering wallet type", (wallet_type, name)) self.logger.info(f"registering wallet type {(wallet_type, name)}")
def loader(): def loader():
plugin = self.get_plugin(name) plugin = self.get_plugin(name)
register_constructor(wallet_type, plugin.wallet_class) register_constructor(wallet_type, plugin.wallet_class)
@ -191,7 +187,7 @@ class Plugins(DaemonThread):
return self.get_plugin(name).keystore_class(d) return self.get_plugin(name).keystore_class(d)
if details[0] == 'hardware': if details[0] == 'hardware':
self.hw_wallets[name] = (gui_good, details) self.hw_wallets[name] = (gui_good, details)
self.print_error("registering hardware %s: %s" %(name, details)) self.logger.info(f"registering hardware {name}: {details}")
register_keystore(details[1], dynamic_constructor) register_keystore(details[1], dynamic_constructor)
def get_plugin(self, name): def get_plugin(self, name):
@ -218,8 +214,7 @@ def run_hook(name, *args):
try: try:
r = f(*args) r = f(*args)
except Exception: except Exception:
print_error("Plugin error") _logger.exception(f"Plugin error. plugin: {p}, hook: {name}")
traceback.print_exc(file=sys.stdout)
r = False r = False
if r: if r:
results.append(r) results.append(r)
@ -229,13 +224,14 @@ def run_hook(name, *args):
return results[0] return results[0]
class BasePlugin(PrintError): class BasePlugin(Logger):
def __init__(self, parent, config, name): def __init__(self, parent, config, name):
self.parent = parent # The plugins object self.parent = parent # The plugins object
self.name = name self.name = name
self.config = config self.config = config
self.wallet = None self.wallet = None
Logger.__init__(self)
# add self to hooks # add self to hooks
for k in dir(self): for k in dir(self):
if k in hook_names: if k in hook_names:
@ -243,9 +239,6 @@ class BasePlugin(PrintError):
l.append((self, getattr(self, k))) l.append((self, getattr(self, k)))
hooks[k] = l hooks[k] = l
def diagnostic_name(self):
return self.name
def __str__(self): def __str__(self):
return self.name return self.name
@ -313,7 +306,7 @@ class HardwarePluginToScan(NamedTuple):
exception: Optional[Exception] exception: Optional[Exception]
class DeviceMgr(ThreadJob, PrintError): class DeviceMgr(ThreadJob):
'''Manages hardware clients. A client communicates over a hardware '''Manages hardware clients. A client communicates over a hardware
channel with the device. channel with the device.
@ -345,7 +338,7 @@ class DeviceMgr(ThreadJob, PrintError):
hidapi are implemented.''' hidapi are implemented.'''
def __init__(self, config): def __init__(self, config):
super(DeviceMgr, self).__init__() ThreadJob.__init__(self)
# Keyed by xpub. The value is the device id # Keyed by xpub. The value is the device id
# has been paired, and None otherwise. # has been paired, and None otherwise.
self.xpub_ids = {} self.xpub_ids = {}
@ -389,7 +382,7 @@ class DeviceMgr(ThreadJob, PrintError):
return client return client
client = plugin.create_client(device, handler) client = plugin.create_client(device, handler)
if client: if client:
self.print_error("Registering", client) self.logger.info(f"Registering {client}")
with self.lock: with self.lock:
self.clients[client] = (device.path, device.id_) self.clients[client] = (device.path, device.id_)
return client return client
@ -444,7 +437,7 @@ class DeviceMgr(ThreadJob, PrintError):
return self.client_lookup(id_) return self.client_lookup(id_)
def client_for_keystore(self, plugin, handler, keystore, force_pair): def client_for_keystore(self, plugin, handler, keystore, force_pair):
self.print_error("getting client for keystore") self.logger.info("getting client for keystore")
if handler is None: if handler is None:
raise Exception(_("Handler not found for") + ' ' + plugin.name + '\n' + _("A library is probably missing.")) raise Exception(_("Handler not found for") + ' ' + plugin.name + '\n' + _("A library is probably missing."))
handler.update_status(False) handler.update_status(False)
@ -457,7 +450,7 @@ class DeviceMgr(ThreadJob, PrintError):
client = self.force_pair_xpub(plugin, handler, info, xpub, derivation, devices) client = self.force_pair_xpub(plugin, handler, info, xpub, derivation, devices)
if client: if client:
handler.update_status(True) handler.update_status(True)
self.print_error("end client for keystore") self.logger.info("end client for keystore")
return client return client
def client_by_xpub(self, plugin, xpub, handler, devices): def client_by_xpub(self, plugin, xpub, handler, devices):
@ -518,7 +511,7 @@ class DeviceMgr(ThreadJob, PrintError):
try: try:
client = self.create_client(device, handler, plugin) client = self.create_client(device, handler, plugin)
except Exception as e: except Exception as e:
self.print_error(f'failed to create client for {plugin.name} at {device.path}: {repr(e)}') self.logger.error(f'failed to create client for {plugin.name} at {device.path}: {repr(e)}')
if include_failing_clients: if include_failing_clients:
infos.append(DeviceInfo(device=device, exception=e)) infos.append(DeviceInfo(device=device, exception=e))
continue continue
@ -595,7 +588,7 @@ class DeviceMgr(ThreadJob, PrintError):
return devices return devices
def scan_devices(self): def scan_devices(self):
self.print_error("scanning devices...") self.logger.info("scanning devices...")
# First see what's connected that we know about # First see what's connected that we know about
devices = self._scan_devices_with_hid() devices = self._scan_devices_with_hid()
@ -605,8 +598,8 @@ class DeviceMgr(ThreadJob, PrintError):
try: try:
new_devices = f() new_devices = f()
except BaseException as e: except BaseException as e:
self.print_error('custom device enum failed. func {}, error {}' self.logger.error('custom device enum failed. func {}, error {}'
.format(str(f), str(e))) .format(str(f), str(e)))
else: else:
devices.extend(new_devices) devices.extend(new_devices)

14
electrum/plugins/audio_modem/qt.py

@ -9,19 +9,23 @@ from PyQt5.QtWidgets import (QComboBox, QGridLayout, QLabel, QPushButton)
from electrum.plugin import BasePlugin, hook from electrum.plugin import BasePlugin, hook
from electrum.gui.qt.util import WaitingDialog, EnterButton, WindowModalDialog, read_QIcon from electrum.gui.qt.util import WaitingDialog, EnterButton, WindowModalDialog, read_QIcon
from electrum.util import print_msg, print_error
from electrum.i18n import _ from electrum.i18n import _
from electrum.logging import get_logger
_logger = get_logger(__name__)
try: try:
import amodem.audio import amodem.audio
import amodem.main import amodem.main
import amodem.config import amodem.config
print_error('Audio MODEM is available.') _logger.info('Audio MODEM is available.')
amodem.log.addHandler(amodem.logging.StreamHandler(sys.stderr)) amodem.log.addHandler(amodem.logging.StreamHandler(sys.stderr))
amodem.log.setLevel(amodem.logging.INFO) amodem.log.setLevel(amodem.logging.INFO)
except ImportError: except ImportError:
amodem = None amodem = None
print_error('Audio MODEM is not found.') _logger.info('Audio MODEM is not found.')
class Plugin(BasePlugin): class Plugin(BasePlugin):
@ -100,7 +104,7 @@ class Plugin(BasePlugin):
dst = interface.player() dst = interface.player()
amodem.main.send(config=self.modem_config, src=src, dst=dst) amodem.main.send(config=self.modem_config, src=src, dst=dst)
print_msg('Sending:', repr(blob)) _logger.info(f'Sending: {repr(blob)}')
blob = zlib.compress(blob.encode('ascii')) blob = zlib.compress(blob.encode('ascii'))
kbps = self.modem_config.modem_bps / 1e3 kbps = self.modem_config.modem_bps / 1e3
@ -118,7 +122,7 @@ class Plugin(BasePlugin):
def on_finished(blob): def on_finished(blob):
if blob: if blob:
blob = zlib.decompress(blob).decode('ascii') blob = zlib.decompress(blob).decode('ascii')
print_msg('Received:', repr(blob)) _logger.info(f'Received: {repr(blob)}')
parent.setText(blob) parent.setText(blob)
kbps = self.modem_config.modem_bps / 1e3 kbps = self.modem_config.modem_bps / 1e3

12
electrum/plugins/coldcard/cmdline.py

@ -1,6 +1,12 @@
from electrum.plugin import hook from electrum.plugin import hook
from electrum.util import print_msg, raw_input, print_stderr
from electrum.logging import get_logger
from .coldcard import ColdcardPlugin from .coldcard import ColdcardPlugin
from electrum.util import print_msg, print_error, raw_input, print_stderr
_logger = get_logger(__name__)
class ColdcardCmdLineHandler: class ColdcardCmdLineHandler:
@ -24,10 +30,10 @@ class ColdcardCmdLineHandler:
print_stderr(msg) print_stderr(msg)
def show_error(self, msg, blocking=False): def show_error(self, msg, blocking=False):
print_error(msg) print_stderr(msg)
def update_status(self, b): def update_status(self, b):
print_error('hw device status', b) _logger.info(f'hw device status {b}')
def finished(self): def finished(self):
pass pass

33
electrum/plugins/coldcard/coldcard.py

@ -13,12 +13,17 @@ from electrum.keystore import Hardware_KeyStore, xpubkey_to_pubkey, Xpub
from electrum.transaction import Transaction from electrum.transaction import Transaction
from electrum.wallet import Standard_Wallet from electrum.wallet import Standard_Wallet
from electrum.crypto import hash_160 from electrum.crypto import hash_160
from electrum.util import print_error, bfh, bh2u, versiontuple, UserFacingException from electrum.util import bfh, bh2u, versiontuple, UserFacingException
from electrum.base_wizard import ScriptTypeNotSupported from electrum.base_wizard import ScriptTypeNotSupported
from electrum.logging import get_logger
from ..hw_wallet import HW_PluginBase from ..hw_wallet import HW_PluginBase
from ..hw_wallet.plugin import LibraryFoundButUnusable from ..hw_wallet.plugin import LibraryFoundButUnusable
_logger = get_logger(__name__)
try: try:
import hid import hid
from ckcc.protocol import CCProtocolPacker, CCProtocolUnpacker from ckcc.protocol import CCProtocolPacker, CCProtocolUnpacker
@ -114,10 +119,10 @@ class CKCCClient:
or (self.dev.master_fingerprint != expected_xfp) or (self.dev.master_fingerprint != expected_xfp)
or (self.dev.master_xpub != expected_xpub)): or (self.dev.master_xpub != expected_xpub)):
# probably indicating programing error, not hacking # probably indicating programing error, not hacking
print_error("[coldcard]", f"xpubs. reported by device: {self.dev.master_xpub}. " _logger.info(f"xpubs. reported by device: {self.dev.master_xpub}. "
f"stored in file: {expected_xpub}") f"stored in file: {expected_xpub}")
raise RuntimeError("Expecting 0x%08x but that's not whats connected?!" % raise RuntimeError("Expecting 0x%08x but that's not what's connected?!" %
expected_xfp) expected_xfp)
# check signature over session key # check signature over session key
# - mitm might have lied about xfp and xpub up to here # - mitm might have lied about xfp and xpub up to here
@ -127,7 +132,7 @@ class CKCCClient:
self._expected_device = ex self._expected_device = ex
print_error("[coldcard]", "Successfully verified against MiTM") _logger.info("Successfully verified against MiTM")
def is_pairable(self): def is_pairable(self):
# can't do anything w/ devices that aren't setup (but not normally reachable) # can't do anything w/ devices that aren't setup (but not normally reachable)
@ -181,7 +186,7 @@ class CKCCClient:
def get_xpub(self, bip32_path, xtype): def get_xpub(self, bip32_path, xtype):
assert xtype in ColdcardPlugin.SUPPORTED_XTYPES assert xtype in ColdcardPlugin.SUPPORTED_XTYPES
print_error('[coldcard]', 'Derive xtype = %r' % xtype) _logger.info('Derive xtype = %r' % xtype)
xpub = self.dev.send_recv(CCProtocolPacker.get_xpub(bip32_path), timeout=5000) xpub = self.dev.send_recv(CCProtocolPacker.get_xpub(bip32_path), timeout=5000)
# TODO handle timeout? # TODO handle timeout?
# change type of xpub to the requested type # change type of xpub to the requested type
@ -296,7 +301,7 @@ class Coldcard_KeyStore(Hardware_KeyStore):
return rv return rv
def give_error(self, message, clear_client=False): def give_error(self, message, clear_client=False):
print_error(message) self.logger.info(message)
if not self.ux_busy: if not self.ux_busy:
self.handler.show_error(message) self.handler.show_error(message)
else: else:
@ -363,7 +368,7 @@ class Coldcard_KeyStore(Hardware_KeyStore):
except (CCUserRefused, CCBusyError) as exc: except (CCUserRefused, CCBusyError) as exc:
self.handler.show_error(str(exc)) self.handler.show_error(str(exc))
except CCProtoError as exc: except CCProtoError as exc:
traceback.print_exc(file=sys.stderr) self.logger.exception('Error showing address')
self.handler.show_error('{}\n\n{}'.format( self.handler.show_error('{}\n\n{}'.format(
_('Error showing address') + ':', str(exc))) _('Error showing address') + ':', str(exc)))
except Exception as e: except Exception as e:
@ -546,11 +551,11 @@ class Coldcard_KeyStore(Hardware_KeyStore):
self.handler.finished() self.handler.finished()
except (CCUserRefused, CCBusyError) as exc: except (CCUserRefused, CCBusyError) as exc:
print_error('[coldcard]', 'Did not sign:', str(exc)) self.logger.info(f'Did not sign: {exc}')
self.handler.show_error(str(exc)) self.handler.show_error(str(exc))
return return
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
self.give_error(e, True) self.give_error(e, True)
return return
@ -581,11 +586,11 @@ class Coldcard_KeyStore(Hardware_KeyStore):
finally: finally:
self.handler.finished() self.handler.finished()
except CCProtoError as exc: except CCProtoError as exc:
traceback.print_exc(file=sys.stderr) self.logger.exception('Error showing address')
self.handler.show_error('{}\n\n{}'.format( self.handler.show_error('{}\n\n{}'.format(
_('Error showing address') + ':', str(exc))) _('Error showing address') + ':', str(exc)))
except BaseException as exc: except BaseException as exc:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
self.handler.show_error(exc) self.handler.show_error(exc)
@ -651,7 +656,7 @@ class ColdcardPlugin(HW_PluginBase):
is_simulator=(device.product_key[1] == CKCC_SIMULATED_PID)) is_simulator=(device.product_key[1] == CKCC_SIMULATED_PID))
return rv return rv
except: except:
self.print_error('late failure connecting to device?') self.logger.info('late failure connecting to device?')
return None return None
def setup_device(self, device_info, wizard, purpose): def setup_device(self, device_info, wizard, purpose):

16
electrum/plugins/cosigner_pool/qt.py

@ -74,12 +74,12 @@ class Listener(util.DaemonThread):
try: try:
message = server.get(keyhash) message = server.get(keyhash)
except Exception as e: except Exception as e:
self.print_error("cannot contact cosigner pool") self.logger.info("cannot contact cosigner pool")
time.sleep(30) time.sleep(30)
continue continue
if message: if message:
self.received.add(keyhash) self.received.add(keyhash)
self.print_error("received message for", keyhash) self.logger.info(f"received message for {keyhash}")
self.parent.obj.cosigner_receive_signal.emit( self.parent.obj.cosigner_receive_signal.emit(
keyhash, message) keyhash, message)
# poll every 30 seconds # poll every 30 seconds
@ -121,11 +121,11 @@ class Plugin(BasePlugin):
if type(wallet) != Multisig_Wallet: if type(wallet) != Multisig_Wallet:
return return
if self.listener is None: if self.listener is None:
self.print_error("starting listener") self.logger.info("starting listener")
self.listener = Listener(self) self.listener = Listener(self)
self.listener.start() self.listener.start()
elif self.listener: elif self.listener:
self.print_error("shutting down listener") self.logger.info("shutting down listener")
self.listener.stop() self.listener.stop()
self.listener = None self.listener = None
self.keys = [] self.keys = []
@ -176,7 +176,7 @@ class Plugin(BasePlugin):
_("Open your cosigner wallet to retrieve it.")) _("Open your cosigner wallet to retrieve it."))
def on_failure(exc_info): def on_failure(exc_info):
e = exc_info[1] e = exc_info[1]
try: traceback.print_exception(*exc_info) try: self.logger.error("on_failure", exc_info=exc_info)
except OSError: pass except OSError: pass
window.show_error(_("Failed to send transaction to cosigning pool") + ':\n' + str(e)) window.show_error(_("Failed to send transaction to cosigning pool") + ':\n' + str(e))
@ -193,12 +193,12 @@ class Plugin(BasePlugin):
WaitingDialog(window, msg, task, on_success, on_failure) WaitingDialog(window, msg, task, on_success, on_failure)
def on_receive(self, keyhash, message): def on_receive(self, keyhash, message):
self.print_error("signal arrived for", keyhash) self.logger.info("signal arrived for", keyhash)
for key, _hash, window in self.keys: for key, _hash, window in self.keys:
if _hash == keyhash: if _hash == keyhash:
break break
else: else:
self.print_error("keyhash not found") self.logger.info("keyhash not found")
return return
wallet = window.wallet wallet = window.wallet
@ -225,7 +225,7 @@ class Plugin(BasePlugin):
privkey = BIP32Node.from_xkey(xprv).eckey privkey = BIP32Node.from_xkey(xprv).eckey
message = bh2u(privkey.decrypt_message(message)) message = bh2u(privkey.decrypt_message(message))
except Exception as e: except Exception as e:
traceback.print_exc(file=sys.stdout) self.logger.exception('')
window.show_error(_('Error decrypting message') + ':\n' + str(e)) window.show_error(_('Error decrypting message') + ':\n' + str(e))
return return

15
electrum/plugins/digitalbitbox/digitalbitbox.py

@ -27,9 +27,14 @@ from electrum.transaction import Transaction
from electrum.i18n import _ from electrum.i18n import _
from electrum.keystore import Hardware_KeyStore from electrum.keystore import Hardware_KeyStore
from ..hw_wallet import HW_PluginBase from ..hw_wallet import HW_PluginBase
from electrum.util import print_error, to_string, UserCancelled, UserFacingException from electrum.util import to_string, UserCancelled, UserFacingException
from electrum.base_wizard import ScriptTypeNotSupported, HWD_SETUP_NEW_WALLET from electrum.base_wizard import ScriptTypeNotSupported, HWD_SETUP_NEW_WALLET
from electrum.network import Network from electrum.network import Network
from electrum.logging import get_logger
_logger = get_logger(__name__)
try: try:
import hid import hid
@ -406,7 +411,7 @@ class DigitalBitbox_Client():
r = to_string(r, 'utf8') r = to_string(r, 'utf8')
reply = json.loads(r) reply = json.loads(r)
except Exception as e: except Exception as e:
print_error('Exception caught ' + repr(e)) _logger.info(f'Exception caught {repr(e)}')
return reply return reply
@ -431,7 +436,7 @@ class DigitalBitbox_Client():
if 'error' in reply: if 'error' in reply:
self.password = None self.password = None
except Exception as e: except Exception as e:
print_error('Exception caught ' + repr(e)) _logger.info(f'Exception caught {repr(e)}')
return reply return reply
@ -679,7 +684,7 @@ class DigitalBitbox_KeyStore(Hardware_KeyStore):
except BaseException as e: except BaseException as e:
self.give_error(e, True) self.give_error(e, True)
else: else:
print_error("Transaction is_complete", tx.is_complete()) _logger.info("Transaction is_complete {tx.is_complete()}")
tx.raw = tx.serialize() tx.raw = tx.serialize()
@ -746,7 +751,7 @@ class DigitalBitboxPlugin(HW_PluginBase):
) )
try: try:
text = Network.send_http_on_proxy('post', url, body=args.encode('ascii'), headers={'content-type': 'application/x-www-form-urlencoded'}) text = Network.send_http_on_proxy('post', url, body=args.encode('ascii'), headers={'content-type': 'application/x-www-form-urlencoded'})
print_error('digitalbitbox reply from server', text) _logger.info(f'digitalbitbox reply from server {text}')
except Exception as e: except Exception as e:
self.handler.show_error(repr(e)) # repr because str(Exception()) == '' self.handler.show_error(repr(e)) # repr because str(Exception()) == ''

22
electrum/plugins/email_requests/qt.py

@ -41,19 +41,21 @@ from PyQt5.QtCore import QObject, pyqtSignal, QThread
from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QLineEdit, from PyQt5.QtWidgets import (QVBoxLayout, QLabel, QGridLayout, QLineEdit,
QInputDialog) QInputDialog)
from electrum.gui.qt.util import (EnterButton, Buttons, CloseButton, OkButton,
WindowModalDialog, get_parent_main_window)
from electrum.plugin import BasePlugin, hook from electrum.plugin import BasePlugin, hook
from electrum.paymentrequest import PaymentRequest from electrum.paymentrequest import PaymentRequest
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import PrintError from electrum.logging import Logger
from ...gui.qt.util import (EnterButton, Buttons, CloseButton, OkButton,
WindowModalDialog, get_parent_main_window)
class Processor(threading.Thread, PrintError): class Processor(threading.Thread, Logger):
polling_interval = 5*60 polling_interval = 5*60
def __init__(self, imap_server, username, password, callback): def __init__(self, imap_server, username, password, callback):
threading.Thread.__init__(self) threading.Thread.__init__(self)
Logger.__init__(self)
self.daemon = True self.daemon = True
self.username = username self.username = username
self.password = password self.password = password
@ -90,7 +92,7 @@ class Processor(threading.Thread, PrintError):
self.M = imaplib.IMAP4_SSL(self.imap_server) self.M = imaplib.IMAP4_SSL(self.imap_server)
self.M.login(self.username, self.password) self.M.login(self.username, self.password)
except BaseException as e: except BaseException as e:
self.print_error('connecting failed: {}'.format(repr(e))) self.logger.info(f'connecting failed: {repr(e)}')
self.connect_wait *= 2 self.connect_wait *= 2
else: else:
self.reset_connect_wait() self.reset_connect_wait()
@ -99,7 +101,7 @@ class Processor(threading.Thread, PrintError):
try: try:
self.poll() self.poll()
except BaseException as e: except BaseException as e:
self.print_error('polling failed: {}'.format(repr(e))) self.logger.info(f'polling failed: {repr(e)}')
break break
time.sleep(self.polling_interval) time.sleep(self.polling_interval)
time.sleep(random.randint(0, self.connect_wait)) time.sleep(random.randint(0, self.connect_wait))
@ -120,7 +122,7 @@ class Processor(threading.Thread, PrintError):
s.sendmail(self.username, [recipient], msg.as_string()) s.sendmail(self.username, [recipient], msg.as_string())
s.quit() s.quit()
except BaseException as e: except BaseException as e:
self.print_error(e) self.logger.info(e)
class QEmailSignalObject(QObject): class QEmailSignalObject(QObject):
@ -151,7 +153,7 @@ class Plugin(BasePlugin):
self.wallets = set() self.wallets = set()
def on_receive(self, pr_str): def on_receive(self, pr_str):
self.print_error('received payment request') self.logger.info('received payment request')
self.pr = PaymentRequest(pr_str) self.pr = PaymentRequest(pr_str)
self.obj.email_new_invoice_signal.emit() self.obj.email_new_invoice_signal.emit()
@ -188,12 +190,12 @@ class Plugin(BasePlugin):
return return
recipient = str(recipient) recipient = str(recipient)
payload = pr.SerializeToString() payload = pr.SerializeToString()
self.print_error('sending mail to', recipient) self.logger.info(f'sending mail to {recipient}')
try: try:
# FIXME this runs in the GUI thread and blocks it... # FIXME this runs in the GUI thread and blocks it...
self.processor.send(recipient, message, payload) self.processor.send(recipient, message, payload)
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
window.show_message(str(e)) window.show_message(str(e))
else: else:
window.show_message(_('Request sent.')) window.show_message(_('Request sent.'))

3
electrum/plugins/greenaddress_instant/qt.py

@ -105,8 +105,7 @@ class Plugin(BasePlugin):
else: else:
d.show_warning(_('{} is not covered by GreenAddress instant confirmation').format(tx.txid()), title=_('Verification failed!')) d.show_warning(_('{} is not covered by GreenAddress instant confirmation').format(tx.txid()), title=_('Verification failed!'))
except BaseException as e: except BaseException as e:
import traceback self.logger.exception('')
traceback.print_exc(file=sys.stdout)
d.show_error(str(e)) d.show_error(str(e))
finally: finally:
d.verify_button.setText(self.button_label) d.verify_button.setText(self.button_label)

8
electrum/plugins/hw_wallet/cmdline.py

@ -1,4 +1,8 @@
from electrum.util import print_error, print_stderr, raw_input from electrum.util import print_stderr, raw_input
from electrum.logging import get_logger
_logger = get_logger(__name__)
class CmdLineHandler: class CmdLineHandler:
@ -40,7 +44,7 @@ class CmdLineHandler:
print_stderr(msg) print_stderr(msg)
def update_status(self, b): def update_status(self, b):
print_error('hw device status', b) _logger.info(f'hw device status {b}')
def finished(self): def finished(self):
pass pass

2
electrum/plugins/hw_wallet/plugin.py

@ -111,7 +111,7 @@ class HW_PluginBase(BasePlugin):
_("Library version for '{}' is incompatible.").format(self.name) _("Library version for '{}' is incompatible.").format(self.name)
+ '\nInstalled: {}, Needed: {} <= x < {}' + '\nInstalled: {}, Needed: {} <= x < {}'
.format(library_version, version_str(self.minimum_library), max_version_str)) .format(library_version, version_str(self.minimum_library), max_version_str))
self.print_stderr(self.libraries_available_message) self.logger.warning(self.libraries_available_message)
return False return False
return True return True

8
electrum/plugins/hw_wallet/qt.py

@ -35,11 +35,12 @@ from electrum.gui.qt.util import (read_QIcon, WWLabel, OkButton, WindowModalDial
Buttons, CancelButton, TaskThread) Buttons, CancelButton, TaskThread)
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import PrintError from electrum.logging import Logger
# The trickiest thing about this handler was getting windows properly # The trickiest thing about this handler was getting windows properly
# parented on macOS. # parented on macOS.
class QtHandlerBase(QObject, PrintError): class QtHandlerBase(QObject, Logger):
'''An interface between the GUI (here, QT) and the device handling '''An interface between the GUI (here, QT) and the device handling
logic for handling I/O.''' logic for handling I/O.'''
@ -53,7 +54,8 @@ class QtHandlerBase(QObject, PrintError):
status_signal = pyqtSignal(object) status_signal = pyqtSignal(object)
def __init__(self, win, device): def __init__(self, win, device):
super(QtHandlerBase, self).__init__() QObject.__init__(self)
Logger.__init__(self)
self.clear_signal.connect(self.clear_dialog) self.clear_signal.connect(self.clear_dialog)
self.error_signal.connect(self.error_dialog) self.error_signal.connect(self.error_dialog)
self.message_signal.connect(self.message_dialog) self.message_signal.connect(self.message_dialog)

14
electrum/plugins/keepkey/clientbase.py

@ -3,9 +3,10 @@ from struct import pack
from electrum import ecc from electrum import ecc
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import PrintError, UserCancelled from electrum.util import UserCancelled
from electrum.keystore import bip39_normalize_passphrase from electrum.keystore import bip39_normalize_passphrase
from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32 from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32
from electrum.logging import Logger
class GuiMixin(object): class GuiMixin(object):
@ -93,7 +94,7 @@ class GuiMixin(object):
return self.proto.CharacterAck(**char_info) return self.proto.CharacterAck(**char_info)
class KeepKeyClientBase(GuiMixin, PrintError): class KeepKeyClientBase(GuiMixin, Logger):
def __init__(self, handler, plugin, proto): def __init__(self, handler, plugin, proto):
assert hasattr(self, 'tx_api') # ProtocolMixin already constructed? assert hasattr(self, 'tx_api') # ProtocolMixin already constructed?
@ -104,6 +105,7 @@ class KeepKeyClientBase(GuiMixin, PrintError):
self.types = plugin.types self.types = plugin.types
self.msg = None self.msg = None
self.creating_wallet = False self.creating_wallet = False
Logger.__init__(self)
self.used() self.used()
def __str__(self): def __str__(self):
@ -137,7 +139,7 @@ class KeepKeyClientBase(GuiMixin, PrintError):
def timeout(self, cutoff): def timeout(self, cutoff):
'''Time out the client if the last operation was before cutoff.''' '''Time out the client if the last operation was before cutoff.'''
if self.last_operation < cutoff: if self.last_operation < cutoff:
self.print_error("timed out") self.logger.info("timed out")
self.clear_session() self.clear_session()
@staticmethod @staticmethod
@ -190,13 +192,13 @@ class KeepKeyClientBase(GuiMixin, PrintError):
def clear_session(self): def clear_session(self):
'''Clear the session to force pin (and passphrase if enabled) '''Clear the session to force pin (and passphrase if enabled)
re-entry. Does not leak exceptions.''' re-entry. Does not leak exceptions.'''
self.print_error("clear session:", self) self.logger.info(f"clear session: {self}")
self.prevent_timeouts() self.prevent_timeouts()
try: try:
super(KeepKeyClientBase, self).clear_session() super(KeepKeyClientBase, self).clear_session()
except BaseException as e: except BaseException as e:
# If the device was removed it has the same effect... # If the device was removed it has the same effect...
self.print_error("clear_session: ignoring error", str(e)) self.logger.info(f"clear_session: ignoring error {e}")
def get_public_node(self, address_n, creating): def get_public_node(self, address_n, creating):
self.creating_wallet = creating self.creating_wallet = creating
@ -204,7 +206,7 @@ class KeepKeyClientBase(GuiMixin, PrintError):
def close(self): def close(self):
'''Called when Our wallet was closed or the device removed.''' '''Called when Our wallet was closed or the device removed.'''
self.print_error("closing client") self.logger.info("closing client")
self.clear_session() self.clear_session()
# Release the device # Release the device
self.transport.close() self.transport.close()

18
electrum/plugins/keepkey/keepkey.py

@ -108,7 +108,7 @@ class KeepKeyPlugin(HW_PluginBase):
return WebUsbTransport(device) return WebUsbTransport(device)
def _try_hid(self, device): def _try_hid(self, device):
self.print_error("Trying to connect over USB...") self.logger.info("Trying to connect over USB...")
if device.interface_number == 1: if device.interface_number == 1:
pair = [None, device.path] pair = [None, device.path]
else: else:
@ -119,15 +119,15 @@ class KeepKeyPlugin(HW_PluginBase):
except BaseException as e: except BaseException as e:
# see fdb810ba622dc7dbe1259cbafb5b28e19d2ab114 # see fdb810ba622dc7dbe1259cbafb5b28e19d2ab114
# raise # raise
self.print_error("cannot connect at", device.path, str(e)) self.logger.info(f"cannot connect at {device.path} {e}")
return None return None
def _try_webusb(self, device): def _try_webusb(self, device):
self.print_error("Trying to connect over WebUSB...") self.logger.info("Trying to connect over WebUSB...")
try: try:
return self.webusb_transport(device) return self.webusb_transport(device)
except BaseException as e: except BaseException as e:
self.print_error("cannot connect at", device.path, str(e)) self.logger.info(f"cannot connect at {device.path} {e}")
return None return None
def create_client(self, device, handler): def create_client(self, device, handler):
@ -137,10 +137,10 @@ class KeepKeyPlugin(HW_PluginBase):
transport = self._try_hid(device) transport = self._try_hid(device)
if not transport: if not transport:
self.print_error("cannot connect to device") self.logger.info("cannot connect to device")
return return
self.print_error("connected to device at", device.path) self.logger.info(f"connected to device at {device.path}")
client = self.client_class(transport, handler, self) client = self.client_class(transport, handler, self)
@ -148,14 +148,14 @@ class KeepKeyPlugin(HW_PluginBase):
try: try:
client.ping('t') client.ping('t')
except BaseException as e: except BaseException as e:
self.print_error("ping failed", str(e)) self.logger.info(f"ping failed {e}")
return None return None
if not client.atleast_version(*self.minimum_firmware): if not client.atleast_version(*self.minimum_firmware):
msg = (_('Outdated {} firmware for device labelled {}. Please ' msg = (_('Outdated {} firmware for device labelled {}. Please '
'download the updated firmware from {}') 'download the updated firmware from {}')
.format(self.device, client.label(), self.firmware_URL)) .format(self.device, client.label(), self.firmware_URL))
self.print_error(msg) self.logger.info(msg)
if handler: if handler:
handler.show_error(msg) handler.show_error(msg)
else: else:
@ -215,7 +215,7 @@ class KeepKeyPlugin(HW_PluginBase):
except UserCancelled: except UserCancelled:
exit_code = 1 exit_code = 1
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
handler.show_error(str(e)) handler.show_error(str(e))
exit_code = 1 exit_code = 1
finally: finally:

2
electrum/plugins/labels/cmdline.py

@ -8,4 +8,4 @@ class Plugin(LabelsPlugin):
self.start_wallet(wallet) self.start_wallet(wallet)
def on_pulled(self, wallet): def on_pulled(self, wallet):
self.print_error('labels pulled from server') self.logger.info('labels pulled from server')

2
electrum/plugins/labels/kivy.py

@ -9,6 +9,6 @@ class Plugin(LabelsPlugin):
self.start_wallet(wallet) self.start_wallet(wallet)
def on_pulled(self, wallet): def on_pulled(self, wallet):
self.print_error('on pulled') self.logger.info('on pulled')
self.window._trigger_update_history() self.window._trigger_update_history()

16
electrum/plugins/labels/labels.py

@ -53,7 +53,7 @@ class LabelsPlugin(BasePlugin):
return nonce return nonce
def set_nonce(self, wallet, nonce): def set_nonce(self, wallet, nonce):
self.print_error("set", wallet.basename(), "nonce to", nonce) self.logger.info(f"set {wallet.basename()} nonce to {nonce}")
wallet.storage.put("wallet_nonce", nonce) wallet.storage.put("wallet_nonce", nonce)
@hook @hook
@ -109,7 +109,7 @@ class LabelsPlugin(BasePlugin):
encoded_key = self.encode(wallet, key) encoded_key = self.encode(wallet, key)
encoded_value = self.encode(wallet, value) encoded_value = self.encode(wallet, value)
except: except:
self.print_error('cannot encode', repr(key), repr(value)) self.logger.info(f'cannot encode {repr(key)} {repr(value)}')
continue continue
bundle["labels"].append({'encryptedLabel': encoded_value, bundle["labels"].append({'encryptedLabel': encoded_value,
'externalId': encoded_key}) 'externalId': encoded_key})
@ -121,13 +121,13 @@ class LabelsPlugin(BasePlugin):
raise Exception('Wallet {} not loaded'.format(wallet)) raise Exception('Wallet {} not loaded'.format(wallet))
wallet_id = wallet_data[2] wallet_id = wallet_data[2]
nonce = 1 if force else self.get_nonce(wallet) - 1 nonce = 1 if force else self.get_nonce(wallet) - 1
self.print_error("asking for labels since nonce", nonce) self.logger.info(f"asking for labels since nonce {nonce}")
try: try:
response = await self.do_get("/labels/since/%d/for/%s" % (nonce, wallet_id)) response = await self.do_get("/labels/since/%d/for/%s" % (nonce, wallet_id))
except Exception as e: except Exception as e:
raise ErrorConnectingServer(e) from e raise ErrorConnectingServer(e) from e
if response["labels"] is None: if response["labels"] is None:
self.print_error('no new labels') self.logger.info('no new labels')
return return
result = {} result = {}
for label in response["labels"]: for label in response["labels"]:
@ -140,7 +140,7 @@ class LabelsPlugin(BasePlugin):
json.dumps(key) json.dumps(key)
json.dumps(value) json.dumps(value)
except: except:
self.print_error('error: no json', key) self.logger.info(f'error: no json {key}')
continue continue
result[key] = value result[key] = value
@ -148,7 +148,7 @@ class LabelsPlugin(BasePlugin):
if force or not wallet.labels.get(key): if force or not wallet.labels.get(key):
wallet.labels[key] = value wallet.labels[key] = value
self.print_error("received %d labels" % len(response)) self.logger.info(f"received {len(response)} labels")
# do not write to disk because we're in a daemon thread # do not write to disk because we're in a daemon thread
wallet.storage.put('labels', wallet.labels) wallet.storage.put('labels', wallet.labels)
self.set_nonce(wallet, response["nonce"] + 1) self.set_nonce(wallet, response["nonce"] + 1)
@ -160,7 +160,7 @@ class LabelsPlugin(BasePlugin):
try: try:
await self.pull_thread(wallet, force) await self.pull_thread(wallet, force)
except ErrorConnectingServer as e: except ErrorConnectingServer as e:
self.print_error(str(e)) self.logger.info(str(e))
def pull(self, wallet, force): def pull(self, wallet, force):
if not wallet.network: raise Exception(_('You are offline.')) if not wallet.network: raise Exception(_('You are offline.'))
@ -173,7 +173,7 @@ class LabelsPlugin(BasePlugin):
def start_wallet(self, wallet): def start_wallet(self, wallet):
if not wallet.network: return # 'offline' mode if not wallet.network: return # 'offline' mode
nonce = self.get_nonce(wallet) nonce = self.get_nonce(wallet)
self.print_error("wallet", wallet.basename(), "nonce is", nonce) self.logger.info(f"wallet {wallet.basename()} nonce is {nonce}")
mpk = wallet.get_fingerprint() mpk = wallet.get_fingerprint()
if not mpk: if not mpk:
return return

6
electrum/plugins/labels/qt.py

@ -58,9 +58,9 @@ class Plugin(LabelsPlugin):
def done_processing_success(self, dialog, result): def done_processing_success(self, dialog, result):
dialog.show_message(_("Your labels have been synchronised.")) dialog.show_message(_("Your labels have been synchronised."))
def done_processing_error(self, dialog, result): def done_processing_error(self, dialog, exc_info):
traceback.print_exception(*result, file=sys.stderr) self.logger.error("Error synchronising labels", exc_info=exc_info)
dialog.show_error(_("Error synchronising labels") + ':\n' + str(result[:2])) dialog.show_error(_("Error synchronising labels") + f':\n{repr(exc_info[1])}')
@hook @hook
def load_wallet(self, wallet, window): def load_wallet(self, wallet, window):

10
electrum/plugins/ledger/auth2fa.py

@ -13,10 +13,13 @@ from PyQt5.QtCore import QThread, pyqtSignal
from btchip.btchip import BTChipException from btchip.btchip import BTChipException
from electrum.gui.qt.qrcodewidget import QRCodeWidget
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import print_msg
from electrum import constants, bitcoin from electrum import constants, bitcoin
from electrum.gui.qt.qrcodewidget import QRCodeWidget from electrum.logging import get_logger
_logger = get_logger(__name__)
DEBUG = False DEBUG = False
@ -354,4 +357,5 @@ class LedgerWebSocket(QThread):
def debug_msg(*args): def debug_msg(*args):
if DEBUG: if DEBUG:
print_msg(*args) str_ = " ".join([str(item) for item in args])
_logger.debug(str_)

17
electrum/plugins/ledger/ledger.py

@ -10,12 +10,17 @@ from electrum.i18n import _
from electrum.keystore import Hardware_KeyStore from electrum.keystore import Hardware_KeyStore
from electrum.transaction import Transaction from electrum.transaction import Transaction
from electrum.wallet import Standard_Wallet from electrum.wallet import Standard_Wallet
from electrum.util import print_error, bfh, bh2u, versiontuple, UserFacingException from electrum.util import bfh, bh2u, versiontuple, UserFacingException
from electrum.base_wizard import ScriptTypeNotSupported from electrum.base_wizard import ScriptTypeNotSupported
from electrum.logging import get_logger
from ..hw_wallet import HW_PluginBase from ..hw_wallet import HW_PluginBase
from ..hw_wallet.plugin import is_any_tx_output_on_change_branch from ..hw_wallet.plugin import is_any_tx_output_on_change_branch
_logger = get_logger(__name__)
try: try:
import hid import hid
from btchip.btchipComm import HIDDongleHIDAPI, DongleWait from btchip.btchipComm import HIDDongleHIDAPI, DongleWait
@ -236,7 +241,7 @@ class Ledger_KeyStore(Hardware_KeyStore):
return self.plugin.get_client(self) return self.plugin.get_client(self)
def give_error(self, message, clear_client = False): def give_error(self, message, clear_client = False):
print_error(message) _logger.info(message)
if not self.signing: if not self.signing:
self.handler.show_error(message) self.handler.show_error(message)
else: else:
@ -499,10 +504,10 @@ class Ledger_KeyStore(Hardware_KeyStore):
elif e.sw == 0x6982: elif e.sw == 0x6982:
raise # pin lock. decorator will catch it raise # pin lock. decorator will catch it
else: else:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
self.give_error(e, True) self.give_error(e, True)
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stdout) self.logger.exception('')
self.give_error(e, True) self.give_error(e, True)
finally: finally:
self.handler.finished() self.handler.finished()
@ -533,10 +538,10 @@ class Ledger_KeyStore(Hardware_KeyStore):
e, e,
_('Your device might not have support for this functionality.'))) _('Your device might not have support for this functionality.')))
else: else:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
self.handler.show_error(e) self.handler.show_error(e)
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
self.handler.show_error(e) self.handler.show_error(e)
finally: finally:
self.handler.finished() self.handler.finished()

2
electrum/plugins/revealer/qt.py

@ -137,7 +137,7 @@ class Plugin(RevealerPlugin):
try: try:
self.make_digital(self.d) self.make_digital(self.d)
except Exception: except Exception:
traceback.print_exc(file=sys.stdout) self.logger.exception('')
else: else:
self.cypherseed_dialog(window) self.cypherseed_dialog(window)

14
electrum/plugins/safe_t/clientbase.py

@ -3,9 +3,10 @@ from struct import pack
from electrum import ecc from electrum import ecc
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import PrintError, UserCancelled from electrum.util import UserCancelled
from electrum.keystore import bip39_normalize_passphrase from electrum.keystore import bip39_normalize_passphrase
from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32 from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32
from electrum.logging import Logger
class GuiMixin(object): class GuiMixin(object):
@ -95,7 +96,7 @@ class GuiMixin(object):
return self.proto.WordAck(word=word) return self.proto.WordAck(word=word)
class SafeTClientBase(GuiMixin, PrintError): class SafeTClientBase(GuiMixin, Logger):
def __init__(self, handler, plugin, proto): def __init__(self, handler, plugin, proto):
assert hasattr(self, 'tx_api') # ProtocolMixin already constructed? assert hasattr(self, 'tx_api') # ProtocolMixin already constructed?
@ -106,6 +107,7 @@ class SafeTClientBase(GuiMixin, PrintError):
self.types = plugin.types self.types = plugin.types
self.msg = None self.msg = None
self.creating_wallet = False self.creating_wallet = False
Logger.__init__(self)
self.used() self.used()
def __str__(self): def __str__(self):
@ -139,7 +141,7 @@ class SafeTClientBase(GuiMixin, PrintError):
def timeout(self, cutoff): def timeout(self, cutoff):
'''Time out the client if the last operation was before cutoff.''' '''Time out the client if the last operation was before cutoff.'''
if self.last_operation < cutoff: if self.last_operation < cutoff:
self.print_error("timed out") self.logger.info("timed out")
self.clear_session() self.clear_session()
@staticmethod @staticmethod
@ -192,13 +194,13 @@ class SafeTClientBase(GuiMixin, PrintError):
def clear_session(self): def clear_session(self):
'''Clear the session to force pin (and passphrase if enabled) '''Clear the session to force pin (and passphrase if enabled)
re-entry. Does not leak exceptions.''' re-entry. Does not leak exceptions.'''
self.print_error("clear session:", self) self.logger.info(f"clear session: {self}")
self.prevent_timeouts() self.prevent_timeouts()
try: try:
super(SafeTClientBase, self).clear_session() super(SafeTClientBase, self).clear_session()
except BaseException as e: except BaseException as e:
# If the device was removed it has the same effect... # If the device was removed it has the same effect...
self.print_error("clear_session: ignoring error", str(e)) self.logger.info(f"clear_session: ignoring error {e}")
def get_public_node(self, address_n, creating): def get_public_node(self, address_n, creating):
self.creating_wallet = creating self.creating_wallet = creating
@ -206,7 +208,7 @@ class SafeTClientBase(GuiMixin, PrintError):
def close(self): def close(self):
'''Called when Our wallet was closed or the device removed.''' '''Called when Our wallet was closed or the device removed.'''
self.print_error("closing client") self.logger.info("closing client")
self.clear_session() self.clear_session()
# Release the device # Release the device
self.transport.close() self.transport.close()

14
electrum/plugins/safe_t/safe_t.py

@ -115,31 +115,31 @@ class SafeTPlugin(HW_PluginBase):
def create_client(self, device, handler): def create_client(self, device, handler):
try: try:
self.print_error("connecting to device at", device.path) self.logger.info(f"connecting to device at {device.path}")
transport = self.transport_handler.get_transport(device.path) transport = self.transport_handler.get_transport(device.path)
except BaseException as e: except BaseException as e:
self.print_error("cannot connect at", device.path, str(e)) self.logger.info(f"cannot connect at {device.path} {e}")
return None return None
if not transport: if not transport:
self.print_error("cannot connect at", device.path) self.logger.info(f"cannot connect at {device.path}")
return return
self.print_error("connected to device at", device.path) self.logger.info(f"connected to device at {device.path}")
client = self.client_class(transport, handler, self) client = self.client_class(transport, handler, self)
# Try a ping for device sanity # Try a ping for device sanity
try: try:
client.ping('t') client.ping('t')
except BaseException as e: except BaseException as e:
self.print_error("ping failed", str(e)) self.logger.info(f"ping failed {e}")
return None return None
if not client.atleast_version(*self.minimum_firmware): if not client.atleast_version(*self.minimum_firmware):
msg = (_('Outdated {} firmware for device labelled {}. Please ' msg = (_('Outdated {} firmware for device labelled {}. Please '
'download the updated firmware from {}') 'download the updated firmware from {}')
.format(self.device, client.label(), self.firmware_URL)) .format(self.device, client.label(), self.firmware_URL))
self.print_error(msg) self.logger.info(msg)
if handler: if handler:
handler.show_error(msg) handler.show_error(msg)
else: else:
@ -199,7 +199,7 @@ class SafeTPlugin(HW_PluginBase):
except UserCancelled: except UserCancelled:
exit_code = 1 exit_code = 1
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
handler.show_error(str(e)) handler.show_error(str(e))
exit_code = 1 exit_code = 1
finally: finally:

10
electrum/plugins/safe_t/transport.py

@ -1,7 +1,10 @@
from electrum.util import PrintError from electrum.logging import get_logger
class SafeTTransport(PrintError): _logger = get_logger(__name__)
class SafeTTransport:
@staticmethod @staticmethod
def all_transports(): def all_transports():
@ -71,8 +74,7 @@ class SafeTTransport(PrintError):
try: try:
new_devices = transport.enumerate() new_devices = transport.enumerate()
except BaseException as e: except BaseException as e:
self.print_error('enumerate failed for {}. error {}' _logger.info(f'enumerate failed for {transport.__name__}. error {e}')
.format(transport.__name__, str(e)))
else: else:
devices.extend(new_devices) devices.extend(new_devices)
return devices return devices

14
electrum/plugins/trezor/clientbase.py

@ -3,9 +3,10 @@ from struct import pack
from electrum import ecc from electrum import ecc
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import PrintError, UserCancelled, UserFacingException from electrum.util import UserCancelled, UserFacingException
from electrum.keystore import bip39_normalize_passphrase from electrum.keystore import bip39_normalize_passphrase
from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32 as parse_path from electrum.bip32 import BIP32Node, convert_bip32_path_to_list_of_uint32 as parse_path
from electrum.logging import Logger
from trezorlib.client import TrezorClient from trezorlib.client import TrezorClient
from trezorlib.exceptions import TrezorFailure, Cancelled, OutdatedFirmwareError from trezorlib.exceptions import TrezorFailure, Cancelled, OutdatedFirmwareError
@ -26,12 +27,13 @@ MESSAGES = {
} }
class TrezorClientBase(PrintError): class TrezorClientBase(Logger):
def __init__(self, transport, handler, plugin): def __init__(self, transport, handler, plugin):
self.client = TrezorClient(transport, ui=self) self.client = TrezorClient(transport, ui=self)
self.plugin = plugin self.plugin = plugin
self.device = plugin.device self.device = plugin.device
self.handler = handler self.handler = handler
Logger.__init__(self)
self.msg = None self.msg = None
self.creating_wallet = False self.creating_wallet = False
@ -111,7 +113,7 @@ class TrezorClientBase(PrintError):
def timeout(self, cutoff): def timeout(self, cutoff):
'''Time out the client if the last operation was before cutoff.''' '''Time out the client if the last operation was before cutoff.'''
if self.last_operation < cutoff: if self.last_operation < cutoff:
self.print_error("timed out") self.logger.info("timed out")
self.clear_session() self.clear_session()
def i4b(self, x): def i4b(self, x):
@ -158,17 +160,17 @@ class TrezorClientBase(PrintError):
def clear_session(self): def clear_session(self):
'''Clear the session to force pin (and passphrase if enabled) '''Clear the session to force pin (and passphrase if enabled)
re-entry. Does not leak exceptions.''' re-entry. Does not leak exceptions.'''
self.print_error("clear session:", self) self.logger.info(f"clear session: {self}")
self.prevent_timeouts() self.prevent_timeouts()
try: try:
self.client.clear_session() self.client.clear_session()
except BaseException as e: except BaseException as e:
# If the device was removed it has the same effect... # If the device was removed it has the same effect...
self.print_error("clear_session: ignoring error", str(e)) self.logger.info(f"clear_session: ignoring error {e}")
def close(self): def close(self):
'''Called when Our wallet was closed or the device removed.''' '''Called when Our wallet was closed or the device removed.'''
self.print_error("closing client") self.logger.info("closing client")
self.clear_session() self.clear_session()
def is_uptodate(self): def is_uptodate(self):

17
electrum/plugins/trezor/trezor.py

@ -11,11 +11,15 @@ from electrum.plugin import Device
from electrum.transaction import deserialize, Transaction from electrum.transaction import deserialize, Transaction
from electrum.keystore import Hardware_KeyStore, is_xpubkey, parse_xpubkey from electrum.keystore import Hardware_KeyStore, is_xpubkey, parse_xpubkey
from electrum.base_wizard import ScriptTypeNotSupported, HWD_SETUP_NEW_WALLET from electrum.base_wizard import ScriptTypeNotSupported, HWD_SETUP_NEW_WALLET
from electrum.logging import get_logger
from ..hw_wallet import HW_PluginBase from ..hw_wallet import HW_PluginBase
from ..hw_wallet.plugin import (is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data, from ..hw_wallet.plugin import (is_any_tx_output_on_change_branch, trezor_validate_op_return_output_and_get_data,
LibraryFoundButUnusable) LibraryFoundButUnusable)
_logger = get_logger(__name__)
try: try:
import trezorlib import trezorlib
import trezorlib.transport import trezorlib.transport
@ -32,8 +36,7 @@ try:
TREZORLIB = True TREZORLIB = True
except Exception as e: except Exception as e:
import traceback _logger.exception('error importing trezorlib')
traceback.print_exc()
TREZORLIB = False TREZORLIB = False
RECOVERY_TYPE_SCRAMBLED_WORDS, RECOVERY_TYPE_MATRIX = range(2) RECOVERY_TYPE_SCRAMBLED_WORDS, RECOVERY_TYPE_MATRIX = range(2)
@ -145,17 +148,17 @@ class TrezorPlugin(HW_PluginBase):
def create_client(self, device, handler): def create_client(self, device, handler):
try: try:
self.print_error("connecting to device at", device.path) self.logger.info(f"connecting to device at {device.path}")
transport = trezorlib.transport.get_transport(device.path) transport = trezorlib.transport.get_transport(device.path)
except BaseException as e: except BaseException as e:
self.print_error("cannot connect at", device.path, str(e)) self.logger.info(f"cannot connect at {device.path} {e}")
return None return None
if not transport: if not transport:
self.print_error("cannot connect at", device.path) self.logger.info(f"cannot connect at {device.path}")
return return
self.print_error("connected to device at", device.path) self.logger.info(f"connected to device at {device.path}")
# note that this call can still raise! # note that this call can still raise!
return TrezorClientBase(transport, handler, self) return TrezorClientBase(transport, handler, self)
@ -208,7 +211,7 @@ class TrezorPlugin(HW_PluginBase):
except UserCancelled: except UserCancelled:
exit_code = 1 exit_code = 1
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
handler.show_error(str(e)) handler.show_error(str(e))
exit_code = 1 exit_code = 1
finally: finally:

4
electrum/plugins/trustedcoin/cmdline.py

@ -34,12 +34,12 @@ class Plugin(TrustedCoinPlugin):
if not isinstance(wallet, self.wallet_class): if not isinstance(wallet, self.wallet_class):
return return
if not wallet.can_sign_without_server(): if not wallet.can_sign_without_server():
self.print_error("twofactor:sign_tx") self.logger.info("twofactor:sign_tx")
auth_code = None auth_code = None
if wallet.keystores['x3/'].get_tx_derivations(tx): if wallet.keystores['x3/'].get_tx_derivations(tx):
msg = _('Please enter your Google Authenticator code:') msg = _('Please enter your Google Authenticator code:')
auth_code = int(input(msg)) auth_code = int(input(msg))
else: else:
self.print_error("twofactor: xpub3 not needed") self.logger.info("twofactor: xpub3 not needed")
wallet.auth_code = auth_code wallet.auth_code = auth_code

14
electrum/plugins/trustedcoin/qt.py

@ -41,7 +41,9 @@ from electrum.gui.qt.main_window import StatusBarButton
from electrum.gui.qt.installwizard import InstallWizard from electrum.gui.qt.installwizard import InstallWizard
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugin import hook from electrum.plugin import hook
from electrum.util import PrintError, is_valid_email from electrum.util import is_valid_email
from electrum.logging import Logger
from .trustedcoin import TrustedCoinPlugin, server from .trustedcoin import TrustedCoinPlugin, server
@ -50,12 +52,13 @@ class TOS(QTextEdit):
error_signal = pyqtSignal(object) error_signal = pyqtSignal(object)
class HandlerTwoFactor(QObject, PrintError): class HandlerTwoFactor(QObject, Logger):
def __init__(self, plugin, window): def __init__(self, plugin, window):
super().__init__() QObject.__init__(self)
self.plugin = plugin self.plugin = plugin
self.window = window self.window = window
Logger.__init__(self)
def prompt_user_for_otp(self, wallet, tx, on_success, on_failure): def prompt_user_for_otp(self, wallet, tx, on_success, on_failure):
if not isinstance(wallet, self.plugin.wallet_class): if not isinstance(wallet, self.plugin.wallet_class):
@ -63,7 +66,7 @@ class HandlerTwoFactor(QObject, PrintError):
if wallet.can_sign_without_server(): if wallet.can_sign_without_server():
return return
if not wallet.keystores['x3/'].get_tx_derivations(tx): if not wallet.keystores['x3/'].get_tx_derivations(tx):
self.print_error("twofactor: xpub3 not needed") self.logger.info("twofactor: xpub3 not needed")
return return
window = self.window.top_level_window() window = self.window.top_level_window()
auth_code = self.plugin.auth_dialog(window) auth_code = self.plugin.auth_dialog(window)
@ -243,8 +246,7 @@ class Plugin(TrustedCoinPlugin):
try: try:
tos = server.get_terms_of_service() tos = server.get_terms_of_service()
except Exception as e: except Exception as e:
import traceback self.logger.exception('Could not retrieve Terms of Service')
traceback.print_exc(file=sys.stderr)
tos_e.error_signal.emit(_('Could not retrieve Terms of Service:') tos_e.error_signal.emit(_('Could not retrieve Terms of Service:')
+ '\n' + str(e)) + '\n' + str(e))
return return

25
electrum/plugins/trustedcoin/trustedcoin.py

@ -37,17 +37,19 @@ from aiohttp import ClientResponse
from electrum import ecc, constants, keystore, version, bip32, bitcoin from electrum import ecc, constants, keystore, version, bip32, bitcoin
from electrum.bitcoin import TYPE_ADDRESS from electrum.bitcoin import TYPE_ADDRESS
from electrum.bip32 import CKD_pub, BIP32Node, xpub_type from electrum.bip32 import BIP32Node, xpub_type
from electrum.crypto import sha256 from electrum.crypto import sha256
from electrum.transaction import TxOutput from electrum.transaction import TxOutput
from electrum.mnemonic import Mnemonic, seed_type, is_any_2fa_seed_type from electrum.mnemonic import Mnemonic, seed_type, is_any_2fa_seed_type
from electrum.wallet import Multisig_Wallet, Deterministic_Wallet from electrum.wallet import Multisig_Wallet, Deterministic_Wallet
from electrum.i18n import _ from electrum.i18n import _
from electrum.plugin import BasePlugin, hook from electrum.plugin import BasePlugin, hook
from electrum.util import NotEnoughFunds, UserFacingException, PrintError from electrum.util import NotEnoughFunds, UserFacingException
from electrum.storage import STO_EV_USER_PW from electrum.storage import STO_EV_USER_PW
from electrum.network import Network from electrum.network import Network
from electrum.base_wizard import BaseWizard from electrum.base_wizard import BaseWizard
from electrum.logging import Logger
def get_signing_xpub(xtype): def get_signing_xpub(xtype):
if not constants.net.TESTNET: if not constants.net.TESTNET:
@ -117,11 +119,12 @@ class ErrorConnectingServer(Exception):
return f"{header}:\n{reason}" if reason else header return f"{header}:\n{reason}" if reason else header
class TrustedCoinCosignerClient(PrintError): class TrustedCoinCosignerClient(Logger):
def __init__(self, user_agent=None, base_url='https://api.trustedcoin.com/2/'): def __init__(self, user_agent=None, base_url='https://api.trustedcoin.com/2/'):
self.base_url = base_url self.base_url = base_url
self.debug = False self.debug = False
self.user_agent = user_agent self.user_agent = user_agent
Logger.__init__(self)
async def handle_response(self, resp: ClientResponse): async def handle_response(self, resp: ClientResponse):
if resp.status != 200: if resp.status != 200:
@ -142,7 +145,7 @@ class TrustedCoinCosignerClient(PrintError):
raise ErrorConnectingServer('You are offline.') raise ErrorConnectingServer('You are offline.')
url = urljoin(self.base_url, relative_url) url = urljoin(self.base_url, relative_url)
if self.debug: if self.debug:
self.print_error(f'<-- {method} {url} {data}') self.logger.debug(f'<-- {method} {url} {data}')
headers = {} headers = {}
if self.user_agent: if self.user_agent:
headers['user-agent'] = self.user_agent headers['user-agent'] = self.user_agent
@ -167,7 +170,7 @@ class TrustedCoinCosignerClient(PrintError):
raise ErrorConnectingServer(e) raise ErrorConnectingServer(e)
else: else:
if self.debug: if self.debug:
self.print_error(f'--> {response}') self.logger.debug(f'--> {response}')
return response return response
def get_terms_of_service(self, billing_plan='electrum-per-tx-otp'): def get_terms_of_service(self, billing_plan='electrum-per-tx-otp'):
@ -327,14 +330,14 @@ class Wallet_2fa(Multisig_Wallet):
tx = mk_tx(outputs) tx = mk_tx(outputs)
if tx.input_value() >= fee: if tx.input_value() >= fee:
raise raise
self.print_error("not charging for this tx") self.logger.info("not charging for this tx")
else: else:
tx = mk_tx(outputs) tx = mk_tx(outputs)
return tx return tx
def on_otp(self, tx, otp): def on_otp(self, tx, otp):
if not otp: if not otp:
self.print_error("sign_transaction: no auth code") self.logger.info("sign_transaction: no auth code")
return return
otp = int(otp) otp = int(otp)
long_user_id, short_id = self.get_user_id() long_user_id, short_id = self.get_user_id()
@ -349,7 +352,7 @@ class Wallet_2fa(Multisig_Wallet):
if r: if r:
raw_tx = r.get('transaction') raw_tx = r.get('transaction')
tx.update(raw_tx) tx.update(raw_tx)
self.print_error("twofactor: is complete", tx.is_complete()) self.logger.info(f"twofactor: is complete {tx.is_complete()}")
# reset billing_info # reset billing_info
self.billing_info = None self.billing_info = None
self.plugin.start_request_thread(self) self.plugin.start_request_thread(self)
@ -451,7 +454,7 @@ class TrustedCoinPlugin(BasePlugin):
if wallet.can_sign_without_server(): if wallet.can_sign_without_server():
return return
if not wallet.keystores['x3/'].get_tx_derivations(tx): if not wallet.keystores['x3/'].get_tx_derivations(tx):
self.print_error("twofactor: xpub3 not needed") self.logger.info("twofactor: xpub3 not needed")
return return
def wrapper(tx): def wrapper(tx):
self.prompt_user_for_otp(wallet, tx, on_success, on_failure) self.prompt_user_for_otp(wallet, tx, on_success, on_failure)
@ -477,12 +480,12 @@ class TrustedCoinPlugin(BasePlugin):
def request_billing_info(self, wallet: 'Wallet_2fa', *, suppress_connection_error=True): def request_billing_info(self, wallet: 'Wallet_2fa', *, suppress_connection_error=True):
if wallet.can_sign_without_server(): if wallet.can_sign_without_server():
return return
self.print_error("request billing info") self.logger.info("request billing info")
try: try:
billing_info = server.get(wallet.get_user_id()[1]) billing_info = server.get(wallet.get_user_id()[1])
except ErrorConnectingServer as e: except ErrorConnectingServer as e:
if suppress_connection_error: if suppress_connection_error:
self.print_error(str(e)) self.logger.info(str(e))
return return
raise raise
billing_index = billing_info['billing_index'] billing_index = billing_info['billing_index']

29
electrum/simple_config.py

@ -10,9 +10,11 @@ from numbers import Real
from copy import deepcopy from copy import deepcopy
from . import util from . import util
from .util import (user_dir, print_error, PrintError, make_dir, from .util import (user_dir, make_dir,
NoDynamicFeeEstimates, format_fee_satoshis, quantize_feerate) NoDynamicFeeEstimates, format_fee_satoshis, quantize_feerate)
from .i18n import _ from .i18n import _
from .logging import get_logger, Logger
FEE_ETA_TARGETS = [25, 10, 5, 2] FEE_ETA_TARGETS = [25, 10, 5, 2]
FEE_DEPTH_TARGETS = [10000000, 5000000, 2000000, 1000000, 500000, 200000, 100000] FEE_DEPTH_TARGETS = [10000000, 5000000, 2000000, 1000000, 500000, 200000, 100000]
@ -27,6 +29,7 @@ FEERATE_STATIC_VALUES = [1000, 2000, 5000, 10000, 20000, 30000,
config = None config = None
_logger = get_logger(__name__)
def get_config(): def get_config():
@ -42,7 +45,7 @@ def set_config(c):
FINAL_CONFIG_VERSION = 3 FINAL_CONFIG_VERSION = 3
class SimpleConfig(PrintError): class SimpleConfig(Logger):
""" """
The SimpleConfig class is responsible for handling operations involving The SimpleConfig class is responsible for handling operations involving
configuration files. configuration files.
@ -59,6 +62,8 @@ class SimpleConfig(PrintError):
if options is None: if options is None:
options = {} options = {}
Logger.__init__(self)
# This lock needs to be acquired for updating and reading the config in # This lock needs to be acquired for updating and reading the config in
# a thread-safe way. # a thread-safe way.
self.lock = threading.RLock() self.lock = threading.RLock()
@ -119,7 +124,7 @@ class SimpleConfig(PrintError):
path = os.path.join(path, 'simnet') path = os.path.join(path, 'simnet')
make_dir(path, allow_symlink=False) make_dir(path, allow_symlink=False)
self.print_error("electrum directory", path) self.logger.info(f"electrum directory {path}")
return path return path
def rename_config_keys(self, config, keypairs, deprecation_warning=False): def rename_config_keys(self, config, keypairs, deprecation_warning=False):
@ -130,21 +135,21 @@ class SimpleConfig(PrintError):
if new_key not in config: if new_key not in config:
config[new_key] = config[old_key] config[new_key] = config[old_key]
if deprecation_warning: if deprecation_warning:
self.print_stderr('Note that the {} variable has been deprecated. ' self.logger.warning('Note that the {} variable has been deprecated. '
'You should use {} instead.'.format(old_key, new_key)) 'You should use {} instead.'.format(old_key, new_key))
del config[old_key] del config[old_key]
updated = True updated = True
return updated return updated
def set_key(self, key, value, save=True): def set_key(self, key, value, save=True):
if not self.is_modifiable(key): if not self.is_modifiable(key):
self.print_stderr("Warning: not changing config key '%s' set on the command line" % key) self.logger.warning(f"not changing config key '{key}' set on the command line")
return return
try: try:
json.dumps(key) json.dumps(key)
json.dumps(value) json.dumps(value)
except: except:
self.print_error(f"json error: cannot save {repr(key)} ({repr(value)})") self.logger.info(f"json error: cannot save {repr(key)} ({repr(value)})")
return return
self._set_key_in_user_config(key, value, save) self._set_key_in_user_config(key, value, save)
@ -169,7 +174,7 @@ class SimpleConfig(PrintError):
def upgrade(self): def upgrade(self):
with self.lock: with self.lock:
self.print_error('upgrading config') self.logger.info('upgrading config')
self.convert_version_2() self.convert_version_2()
self.convert_version_3() self.convert_version_3()
@ -222,8 +227,8 @@ class SimpleConfig(PrintError):
def get_config_version(self): def get_config_version(self):
config_version = self.get('config_version', 1) config_version = self.get('config_version', 1)
if config_version > FINAL_CONFIG_VERSION: if config_version > FINAL_CONFIG_VERSION:
self.print_stderr('WARNING: config version ({}) is higher than ours ({})' self.logger.warning('config version ({}) is higher than latest ({})'
.format(config_version, FINAL_CONFIG_VERSION)) .format(config_version, FINAL_CONFIG_VERSION))
return config_version return config_version
def is_modifiable(self, key): def is_modifiable(self, key):
@ -276,7 +281,7 @@ class SimpleConfig(PrintError):
self.set_key('recently_open', recent) self.set_key('recently_open', recent)
def set_session_timeout(self, seconds): def set_session_timeout(self, seconds):
self.print_error("session timeout -> %d seconds" % seconds) self.logger.info(f"session timeout -> {seconds} seconds")
self.set_key('session_timeout', seconds) self.set_key('session_timeout', seconds)
def get_session_timeout(self): def get_session_timeout(self):
@ -576,7 +581,7 @@ def read_user_config(path):
data = f.read() data = f.read()
result = json.loads(data) result = json.loads(data)
except: except:
print_error("Warning: Cannot read config file.", config_path) _logger.warning(f"Cannot read config file. {config_path}")
return {} return {}
if not type(result) is dict: if not type(result) is dict:
return {} return {}

12
electrum/storage.py

@ -30,10 +30,11 @@ import base64
import zlib import zlib
from . import ecc from . import ecc
from .util import PrintError, profiler, InvalidPassword, WalletFileException, bfh, standardize_path from .util import profiler, InvalidPassword, WalletFileException, bfh, standardize_path
from .plugin import run_hook, plugin_loaders from .plugin import run_hook, plugin_loaders
from .json_db import JsonDB from .json_db import JsonDB
from .logging import Logger
def get_derivation_used_for_hw_device_encryption(): def get_derivation_used_for_hw_device_encryption():
@ -46,15 +47,16 @@ STO_EV_PLAINTEXT, STO_EV_USER_PW, STO_EV_XPUB_PW = range(0, 3)
class WalletStorage(PrintError): class WalletStorage(Logger):
def __init__(self, path, *, manual_upgrades=False): def __init__(self, path, *, manual_upgrades=False):
Logger.__init__(self)
self.lock = threading.RLock() self.lock = threading.RLock()
self.path = standardize_path(path) self.path = standardize_path(path)
self._file_exists = self.path and os.path.exists(self.path) self._file_exists = self.path and os.path.exists(self.path)
DB_Class = JsonDB DB_Class = JsonDB
self.print_error("wallet path", self.path) self.logger.info(f"wallet path {self.path}")
self.pubkey = None self.pubkey = None
if self.file_exists(): if self.file_exists():
with open(self.path, "r", encoding='utf-8') as f: with open(self.path, "r", encoding='utf-8') as f:
@ -87,7 +89,7 @@ class WalletStorage(PrintError):
def _write(self): def _write(self):
if threading.currentThread().isDaemon(): if threading.currentThread().isDaemon():
self.print_error('warning: daemon thread cannot write db') self.logger.warning('daemon thread cannot write db')
return return
if not self.db.modified(): if not self.db.modified():
return return
@ -105,7 +107,7 @@ class WalletStorage(PrintError):
os.replace(temp_path, self.path) os.replace(temp_path, self.path)
os.chmod(self.path, mode) os.chmod(self.path, mode)
self._file_exists = True self._file_exists = True
self.print_error("saved", self.path) self.logger.info(f"saved {self.path}")
self.db.set_modified(False) self.db.set_modified(False)
def file_exists(self): def file_exists(self):

17
electrum/synchronizer.py

@ -33,6 +33,7 @@ from .transaction import Transaction
from .util import bh2u, make_aiohttp_session, NetworkJobOnDefaultServer from .util import bh2u, make_aiohttp_session, NetworkJobOnDefaultServer
from .bitcoin import address_to_scripthash, is_address from .bitcoin import address_to_scripthash, is_address
from .network import UntrustedServerReturnedError from .network import UntrustedServerReturnedError
from .logging import Logger
if TYPE_CHECKING: if TYPE_CHECKING:
from .network import Network from .network import Network
@ -131,7 +132,7 @@ class Synchronizer(SynchronizerBase):
self.requested_histories = {} self.requested_histories = {}
def diagnostic_name(self): def diagnostic_name(self):
return '{}:{}'.format(self.__class__.__name__, self.wallet.diagnostic_name()) return self.wallet.diagnostic_name()
def is_up_to_date(self): def is_up_to_date(self):
return (not self.requested_addrs return (not self.requested_addrs
@ -148,7 +149,7 @@ class Synchronizer(SynchronizerBase):
self.requested_histories[addr] = status self.requested_histories[addr] = status
h = address_to_scripthash(addr) h = address_to_scripthash(addr)
result = await self.network.get_history_for_scripthash(h) result = await self.network.get_history_for_scripthash(h)
self.print_error("receiving history", addr, len(result)) self.logger.info(f"receiving history {addr} {len(result)}")
hashes = set(map(lambda item: item['tx_hash'], result)) hashes = set(map(lambda item: item['tx_hash'], result))
hist = list(map(lambda item: (item['tx_hash'], item['height']), result)) hist = list(map(lambda item: (item['tx_hash'], item['height']), result))
# tx_fees # tx_fees
@ -156,10 +157,10 @@ class Synchronizer(SynchronizerBase):
tx_fees = dict(filter(lambda x:x[1] is not None, tx_fees)) tx_fees = dict(filter(lambda x:x[1] is not None, tx_fees))
# Check that txids are unique # Check that txids are unique
if len(hashes) != len(result): if len(hashes) != len(result):
self.print_error("error: server history has non-unique txids: %s"% addr) self.logger.info(f"error: server history has non-unique txids: {addr}")
# Check that the status corresponds to what was announced # Check that the status corresponds to what was announced
elif history_status(hist) != status: elif history_status(hist) != status:
self.print_error("error: status mismatch: %s" % addr) self.logger.info(f"error: status mismatch: {addr}")
else: else:
# Store received history # Store received history
self.wallet.receive_history_callback(addr, hist, tx_fees) self.wallet.receive_history_callback(addr, hist, tx_fees)
@ -209,7 +210,7 @@ class Synchronizer(SynchronizerBase):
raise SynchronizerFailure(f"received tx does not match expected txid ({tx_hash} != {tx.txid()})") raise SynchronizerFailure(f"received tx does not match expected txid ({tx_hash} != {tx.txid()})")
tx_height = self.requested_tx.pop(tx_hash) tx_height = self.requested_tx.pop(tx_hash)
self.wallet.receive_tx_callback(tx_hash, tx, tx_height) self.wallet.receive_tx_callback(tx_hash, tx, tx_height)
self.print_error(f"received tx {tx_hash} height: {tx_height} bytes: {len(tx.raw)}") self.logger.info(f"received tx {tx_hash} height: {tx_height} bytes: {len(tx.raw)}")
# callbacks # callbacks
self.wallet.network.trigger_callback('new_transaction', self.wallet, tx) self.wallet.network.trigger_callback('new_transaction', self.wallet, tx)
@ -257,7 +258,7 @@ class Notifier(SynchronizerBase):
await self._add_address(addr) await self._add_address(addr)
async def _on_address_status(self, addr, status): async def _on_address_status(self, addr, status):
self.print_error('new status for addr {}'.format(addr)) self.logger.info(f'new status for addr {addr}')
headers = {'content-type': 'application/json'} headers = {'content-type': 'application/json'}
data = {'address': addr, 'status': status} data = {'address': addr, 'status': status}
for url in self.watched_addresses[addr]: for url in self.watched_addresses[addr]:
@ -266,6 +267,6 @@ class Notifier(SynchronizerBase):
async with session.post(url, json=data, headers=headers) as resp: async with session.post(url, json=data, headers=headers) as resp:
await resp.text() await resp.text()
except Exception as e: except Exception as e:
self.print_error(str(e)) self.logger.info(str(e))
else: else:
self.print_error('Got Response for {}'.format(addr)) self.logger.info(f'Got Response for {addr}')

35
electrum/transaction.py

@ -34,7 +34,7 @@ from typing import (Sequence, Union, NamedTuple, Tuple, Optional, Iterable,
Callable, List, Dict) Callable, List, Dict)
from . import ecc, bitcoin, constants, segwit_addr from . import ecc, bitcoin, constants, segwit_addr
from .util import print_error, profiler, to_bytes, bh2u, bfh from .util import profiler, to_bytes, bh2u, bfh
from .bitcoin import (TYPE_ADDRESS, TYPE_PUBKEY, TYPE_SCRIPT, hash_160, from .bitcoin import (TYPE_ADDRESS, TYPE_PUBKEY, TYPE_SCRIPT, hash_160,
hash160_to_p2sh, hash160_to_p2pkh, hash_to_segwit_addr, hash160_to_p2sh, hash160_to_p2pkh, hash_to_segwit_addr,
hash_encode, var_int, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC, COIN, hash_encode, var_int, TOTAL_COIN_SUPPLY_LIMIT_IN_BTC, COIN,
@ -42,6 +42,10 @@ from .bitcoin import (TYPE_ADDRESS, TYPE_PUBKEY, TYPE_SCRIPT, hash_160,
opcodes, add_number_to_script, base_decode, is_segwit_script_type) opcodes, add_number_to_script, base_decode, is_segwit_script_type)
from .crypto import sha256d from .crypto import sha256d
from .keystore import xpubkey_to_address, xpubkey_to_pubkey from .keystore import xpubkey_to_address, xpubkey_to_pubkey
from .logging import get_logger
_logger = get_logger(__name__)
NO_SIGNATURE = 'ff' NO_SIGNATURE = 'ff'
@ -269,8 +273,7 @@ def parse_scriptSig(d, _bytes):
decoded = [ x for x in script_GetOp(_bytes) ] decoded = [ x for x in script_GetOp(_bytes) ]
except Exception as e: except Exception as e:
# coinbase transactions raise an exception # coinbase transactions raise an exception
print_error("parse_scriptSig: cannot find address in input script (coinbase?)", _logger.info(f"parse_scriptSig: cannot find address in input script (coinbase?) {bh2u(_bytes)}")
bh2u(_bytes))
return return
match = [OPPushDataGeneric] match = [OPPushDataGeneric]
@ -285,7 +288,7 @@ def parse_scriptSig(d, _bytes):
elif len(item) == 34: elif len(item) == 34:
d['type'] = 'p2wsh-p2sh' d['type'] = 'p2wsh-p2sh'
else: else:
print_error("unrecognized txin type", bh2u(item)) _logger.info(f"unrecognized txin type {bh2u(item)}")
elif opcodes.OP_1 <= item[0] <= opcodes.OP_16: elif opcodes.OP_1 <= item[0] <= opcodes.OP_16:
# segwit embedded into p2sh # segwit embedded into p2sh
# witness version 1-16 # witness version 1-16
@ -312,8 +315,7 @@ def parse_scriptSig(d, _bytes):
signatures = parse_sig([sig]) signatures = parse_sig([sig])
pubkey, address = xpubkey_to_address(x_pubkey) pubkey, address = xpubkey_to_address(x_pubkey)
except: except:
print_error("parse_scriptSig: cannot find address in input script (p2pkh?)", _logger.info(f"parse_scriptSig: cannot find address in input script (p2pkh?) {bh2u(_bytes)}")
bh2u(_bytes))
return return
d['type'] = 'p2pkh' d['type'] = 'p2pkh'
d['signatures'] = signatures d['signatures'] = signatures
@ -331,8 +333,8 @@ def parse_scriptSig(d, _bytes):
try: try:
m, n, x_pubkeys, pubkeys, redeem_script = parse_redeemScript_multisig(redeem_script_unsanitized) m, n, x_pubkeys, pubkeys, redeem_script = parse_redeemScript_multisig(redeem_script_unsanitized)
except NotRecognizedRedeemScript: except NotRecognizedRedeemScript:
print_error("parse_scriptSig: cannot find address in input script (p2sh?)", _logger.info(f"parse_scriptSig: cannot find address in input script (p2sh?) {bh2u(_bytes)}")
bh2u(_bytes))
# we could still guess: # we could still guess:
# d['address'] = hash160_to_p2sh(hash_160(decoded[-1][1])) # d['address'] = hash160_to_p2sh(hash_160(decoded[-1][1]))
return return
@ -359,8 +361,7 @@ def parse_scriptSig(d, _bytes):
d['signatures'] = [None] d['signatures'] = [None]
return return
print_error("parse_scriptSig: cannot find address in input script (unknown)", _logger.info(f"parse_scriptSig: cannot find address in input script (unknown) {bh2u(_bytes)}")
bh2u(_bytes))
def parse_redeemScript_multisig(redeem_script: bytes): def parse_redeemScript_multisig(redeem_script: bytes):
@ -445,8 +446,7 @@ def parse_input(vds, full_parse: bool):
try: try:
parse_scriptSig(d, scriptSig) parse_scriptSig(d, scriptSig)
except BaseException: except BaseException:
traceback.print_exc(file=sys.stderr) _logger.exception(f'failed to parse scriptSig {bh2u(scriptSig)}')
print_error('failed to parse scriptSig', bh2u(scriptSig))
return d return d
@ -512,8 +512,7 @@ def parse_witness(vds, txin, full_parse: bool):
txin['type'] = 'unknown' txin['type'] = 'unknown'
except BaseException: except BaseException:
txin['type'] = 'unknown' txin['type'] = 'unknown'
traceback.print_exc(file=sys.stderr) _logger.exception(f"failed to parse witness {txin.get('witness')}")
print_error('failed to parse witness', txin.get('witness'))
def parse_output(vds, i): def parse_output(vds, i):
@ -664,10 +663,10 @@ class Transaction:
try: try:
public_key.verify_message_hash(sig_string, pre_hash) public_key.verify_message_hash(sig_string, pre_hash)
except Exception: except Exception:
traceback.print_exc(file=sys.stderr) _logger.exception('')
continue continue
j = pubkeys.index(pubkey_hex) j = pubkeys.index(pubkey_hex)
print_error("adding sig", i, j, pubkey_hex, sig) _logger.info(f"adding sig {i} {j} {pubkey_hex} {sig}")
self.add_signature_to_txin(i, j, sig) self.add_signature_to_txin(i, j, sig)
break break
# redo raw # redo raw
@ -1138,12 +1137,12 @@ class Transaction:
_pubkey = x_pubkey _pubkey = x_pubkey
else: else:
continue continue
print_error("adding signature for", _pubkey) _logger.info(f"adding signature for {_pubkey}")
sec, compressed = keypairs.get(_pubkey) sec, compressed = keypairs.get(_pubkey)
sig = self.sign_txin(i, sec) sig = self.sign_txin(i, sec)
self.add_signature_to_txin(i, j, sig) self.add_signature_to_txin(i, j, sig)
print_error("is_complete", self.is_complete()) _logger.info(f"is_complete {self.is_complete()}")
self.raw = self.serialize() self.raw = self.serialize()
def sign_txin(self, txin_index, privkey_bytes) -> str: def sign_txin(self, txin_index, privkey_bytes) -> str:

82
electrum/util.py

@ -47,6 +47,7 @@ from aiorpcx import TaskGroup
import certifi import certifi
from .i18n import _ from .i18n import _
from .logging import get_logger, Logger
if TYPE_CHECKING: if TYPE_CHECKING:
from .network import Network from .network import Network
@ -54,6 +55,9 @@ if TYPE_CHECKING:
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
_logger = get_logger(__name__)
def inv_dict(d): def inv_dict(d):
return {v: k for k, v in d.items()} return {v: k for k, v in d.items()}
@ -214,34 +218,15 @@ class MyEncoder(json.JSONEncoder):
return list(obj) return list(obj)
return super().default(obj) return super().default(obj)
class PrintError(object):
'''A handy base class'''
verbosity_filter = ''
def diagnostic_name(self):
return ''
def log_name(self):
msg = self.verbosity_filter or self.__class__.__name__
d = self.diagnostic_name()
if d: msg += "][" + d
return "[%s]" % msg
def print_error(self, *msg):
if self.verbosity_filter in verbosity or verbosity == '*':
print_error(self.log_name(), *msg)
def print_stderr(self, *msg):
print_stderr(self.log_name(), *msg)
def print_msg(self, *msg):
print_msg(self.log_name(), *msg)
class ThreadJob(PrintError): class ThreadJob(Logger):
"""A job that is run periodically from a thread's main loop. run() is """A job that is run periodically from a thread's main loop. run() is
called from that thread's context. called from that thread's context.
""" """
def __init__(self):
Logger.__init__(self)
def run(self): def run(self):
"""Called periodically from the thread""" """Called periodically from the thread"""
pass pass
@ -249,13 +234,14 @@ class ThreadJob(PrintError):
class DebugMem(ThreadJob): class DebugMem(ThreadJob):
'''A handy class for debugging GC memory leaks''' '''A handy class for debugging GC memory leaks'''
def __init__(self, classes, interval=30): def __init__(self, classes, interval=30):
ThreadJob.__init__(self)
self.next_time = 0 self.next_time = 0
self.classes = classes self.classes = classes
self.interval = interval self.interval = interval
def mem_stats(self): def mem_stats(self):
import gc import gc
self.print_error("Start memscan") self.logger.info("Start memscan")
gc.collect() gc.collect()
objmap = defaultdict(list) objmap = defaultdict(list)
for obj in gc.get_objects(): for obj in gc.get_objects():
@ -263,20 +249,20 @@ class DebugMem(ThreadJob):
if isinstance(obj, class_): if isinstance(obj, class_):
objmap[class_].append(obj) objmap[class_].append(obj)
for class_, objs in objmap.items(): for class_, objs in objmap.items():
self.print_error("%s: %d" % (class_.__name__, len(objs))) self.logger.info(f"{class_.__name__}: {len(objs)}")
self.print_error("Finish memscan") self.logger.info("Finish memscan")
def run(self): def run(self):
if time.time() > self.next_time: if time.time() > self.next_time:
self.mem_stats() self.mem_stats()
self.next_time = time.time() + self.interval self.next_time = time.time() + self.interval
class DaemonThread(threading.Thread, PrintError): class DaemonThread(threading.Thread, Logger):
""" daemon thread that terminates cleanly """ """ daemon thread that terminates cleanly """
verbosity_filter = 'd'
def __init__(self): def __init__(self):
threading.Thread.__init__(self) threading.Thread.__init__(self)
Logger.__init__(self)
self.parent_thread = threading.currentThread() self.parent_thread = threading.currentThread()
self.running = False self.running = False
self.running_lock = threading.Lock() self.running_lock = threading.Lock()
@ -296,7 +282,7 @@ class DaemonThread(threading.Thread, PrintError):
try: try:
job.run() job.run()
except Exception as e: except Exception as e:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
def remove_jobs(self, jobs): def remove_jobs(self, jobs):
with self.job_lock: with self.job_lock:
@ -320,22 +306,9 @@ class DaemonThread(threading.Thread, PrintError):
if 'ANDROID_DATA' in os.environ: if 'ANDROID_DATA' in os.environ:
import jnius import jnius
jnius.detach() jnius.detach()
self.print_error("jnius detach") self.logger.info("jnius detach")
self.print_error("stopped") self.logger.info("stopped")
verbosity = ''
def set_verbosity(filters: Union[str, bool]):
global verbosity
if type(filters) is bool: # backwards compat
verbosity = '*' if filters else ''
return
verbosity = filters
def print_error(*args):
if not verbosity: return
print_stderr(*args)
def print_stderr(*args): def print_stderr(*args):
args = [str(item) for item in args] args = [str(item) for item in args]
@ -369,13 +342,14 @@ def constant_time_compare(val1, val2):
# decorator that prints execution time # decorator that prints execution time
_profiler_logger = _logger.getChild('profiler')
def profiler(func): def profiler(func):
def do_profile(args, kw_args): def do_profile(args, kw_args):
name = func.__qualname__ name = func.__qualname__
t0 = time.time() t0 = time.time()
o = func(*args, **kw_args) o = func(*args, **kw_args)
t = time.time() - t0 t = time.time() - t0
print_error("[profiler]", name, "%.4f"%t) _profiler_logger.debug(f"{name} {t:,.4f}")
return o return o
return lambda *args, **kw_args: do_profile(args, kw_args) return lambda *args, **kw_args: do_profile(args, kw_args)
@ -393,7 +367,7 @@ def ensure_sparse_file(filename):
try: try:
os.system('fsutil sparse setflag "{}" 1'.format(filename)) os.system('fsutil sparse setflag "{}" 1'.format(filename))
except Exception as e: except Exception as e:
print_error('error marking file {} as sparse: {}'.format(filename, e)) _logger.info(f'error marking file {filename} as sparse: {e}')
def get_headers_dir(config): def get_headers_dir(config):
@ -893,10 +867,10 @@ def import_meta(path, validater, load_meta):
load_meta(d) load_meta(d)
#backwards compatibility for JSONDecodeError #backwards compatibility for JSONDecodeError
except ValueError: except ValueError:
traceback.print_exc(file=sys.stderr) _logger.exception('')
raise FileImportFailed(_("Invalid JSON code.")) raise FileImportFailed(_("Invalid JSON code."))
except BaseException as e: except BaseException as e:
traceback.print_exc(file=sys.stdout) _logger.exception('')
raise FileImportFailed(e) raise FileImportFailed(e)
@ -905,7 +879,7 @@ def export_meta(meta, fileName):
with open(fileName, 'w+', encoding='utf-8') as f: with open(fileName, 'w+', encoding='utf-8') as f:
json.dump(meta, f, indent=4, sort_keys=True) json.dump(meta, f, indent=4, sort_keys=True)
except (IOError, os.error) as e: except (IOError, os.error) as e:
traceback.print_exc(file=sys.stderr) _logger.exception('')
raise FileExportFailed(e) raise FileExportFailed(e)
@ -928,12 +902,11 @@ def log_exceptions(func):
except asyncio.CancelledError as e: except asyncio.CancelledError as e:
raise raise
except BaseException as e: except BaseException as e:
print_ = self.print_error if hasattr(self, 'print_error') else print_error mylogger = self.logger if hasattr(self, 'logger') else _logger
print_("Exception in", func.__name__, ":", repr(e))
try: try:
traceback.print_exc(file=sys.stderr) mylogger.exception(f"Exception in {func.__name__}: {repr(e)}")
except BaseException as e2: except BaseException as e2:
print_error("traceback.print_exc raised: {}...".format(e2)) print(f"logging exception raised: {repr(e2)}... orig exc: {repr(e)} in {func.__name__}")
raise raise
return wrapper return wrapper
@ -991,12 +964,13 @@ class SilentTaskGroup(TaskGroup):
return super().spawn(*args, **kwargs) return super().spawn(*args, **kwargs)
class NetworkJobOnDefaultServer(PrintError): class NetworkJobOnDefaultServer(Logger):
"""An abstract base class for a job that runs on the main network """An abstract base class for a job that runs on the main network
interface. Every time the main interface changes, the job is interface. Every time the main interface changes, the job is
restarted, and some of its internals are reset. restarted, and some of its internals are reset.
""" """
def __init__(self, network: 'Network'): def __init__(self, network: 'Network'):
Logger.__init__(self)
asyncio.set_event_loop(network.asyncio_loop) asyncio.set_event_loop(network.asyncio_loop)
self.network = network self.network = network
self.interface = None # type: Interface self.interface = None # type: Interface

18
electrum/verifier.py

@ -63,7 +63,7 @@ class SPV(NetworkJobOnDefaultServer):
await group.spawn(self.main) await group.spawn(self.main)
def diagnostic_name(self): def diagnostic_name(self):
return '{}:{}'.format(self.__class__.__name__, self.wallet.diagnostic_name()) return self.wallet.diagnostic_name()
async def main(self): async def main(self):
self.blockchain = self.network.blockchain() self.blockchain = self.network.blockchain()
@ -90,7 +90,7 @@ class SPV(NetworkJobOnDefaultServer):
await self.group.spawn(self.network.request_chunk(tx_height, None, can_return_early=True)) await self.group.spawn(self.network.request_chunk(tx_height, None, can_return_early=True))
continue continue
# request now # request now
self.print_error('requested merkle', tx_hash) self.logger.info(f'requested merkle {tx_hash}')
self.requested_merkle.add(tx_hash) self.requested_merkle.add(tx_hash)
await self.group.spawn(self._request_and_verify_single_proof, tx_hash, tx_height) await self.group.spawn(self._request_and_verify_single_proof, tx_hash, tx_height)
@ -100,14 +100,14 @@ class SPV(NetworkJobOnDefaultServer):
except UntrustedServerReturnedError as e: except UntrustedServerReturnedError as e:
if not isinstance(e.original_exception, aiorpcx.jsonrpc.RPCError): if not isinstance(e.original_exception, aiorpcx.jsonrpc.RPCError):
raise raise
self.print_error('tx {} not at height {}'.format(tx_hash, tx_height)) self.logger.info(f'tx {tx_hash} not at height {tx_height}')
self.wallet.remove_unverified_tx(tx_hash, tx_height) self.wallet.remove_unverified_tx(tx_hash, tx_height)
self.requested_merkle.discard(tx_hash) self.requested_merkle.discard(tx_hash)
return return
# Verify the hash of the server-provided merkle branch to a # Verify the hash of the server-provided merkle branch to a
# transaction matches the merkle root of its block # transaction matches the merkle root of its block
if tx_height != merkle.get('block_height'): if tx_height != merkle.get('block_height'):
self.print_error('requested tx_height {} differs from received tx_height {} for txid {}' self.logger.info('requested tx_height {} differs from received tx_height {} for txid {}'
.format(tx_height, merkle.get('block_height'), tx_hash)) .format(tx_height, merkle.get('block_height'), tx_hash))
tx_height = merkle.get('block_height') tx_height = merkle.get('block_height')
pos = merkle.get('pos') pos = merkle.get('pos')
@ -119,14 +119,14 @@ class SPV(NetworkJobOnDefaultServer):
verify_tx_is_in_block(tx_hash, merkle_branch, pos, header, tx_height) verify_tx_is_in_block(tx_hash, merkle_branch, pos, header, tx_height)
except MerkleVerificationFailure as e: except MerkleVerificationFailure as e:
if self.network.config.get("skipmerklecheck"): if self.network.config.get("skipmerklecheck"):
self.print_error("skipping merkle proof check %s" % tx_hash) self.logger.info(f"skipping merkle proof check {tx_hash}")
else: else:
self.print_error(str(e)) self.logger.info(str(e))
raise GracefulDisconnect(e) raise GracefulDisconnect(e)
# we passed all the tests # we passed all the tests
self.merkle_roots[tx_hash] = header.get('merkle_root') self.merkle_roots[tx_hash] = header.get('merkle_root')
self.requested_merkle.discard(tx_hash) self.requested_merkle.discard(tx_hash)
self.print_error("verified %s" % tx_hash) self.logger.info(f"verified {tx_hash}")
header_hash = hash_header(header) header_hash = hash_header(header)
tx_info = TxMinedInfo(height=tx_height, tx_info = TxMinedInfo(height=tx_height,
timestamp=header.get('timestamp'), timestamp=header.get('timestamp'),
@ -171,10 +171,10 @@ class SPV(NetworkJobOnDefaultServer):
if cur_chain != old_chain: if cur_chain != old_chain:
self.blockchain = cur_chain self.blockchain = cur_chain
above_height = cur_chain.get_height_of_last_common_block_with_chain(old_chain) above_height = cur_chain.get_height_of_last_common_block_with_chain(old_chain)
self.print_error(f"undoing verifications above height {above_height}") self.logger.info(f"undoing verifications above height {above_height}")
tx_hashes = self.wallet.undo_verifications(self.blockchain, above_height) tx_hashes = self.wallet.undo_verifications(self.blockchain, above_height)
for tx_hash in tx_hashes: for tx_hash in tx_hashes:
self.print_error("redoing", tx_hash) self.logger.info(f"redoing {tx_hash}")
self.remove_spv_proof_for_tx(tx_hash) self.remove_spv_proof_for_tx(tx_hash)
def remove_spv_proof_for_tx(self, tx_hash): def remove_spv_proof_for_tx(self, tx_hash):

20
electrum/wallet.py

@ -41,11 +41,11 @@ from decimal import Decimal
from typing import TYPE_CHECKING, List, Optional, Tuple, Union, NamedTuple from typing import TYPE_CHECKING, List, Optional, Tuple, Union, NamedTuple
from .i18n import _ from .i18n import _
from .util import (NotEnoughFunds, PrintError, UserCancelled, profiler, from .util import (NotEnoughFunds, UserCancelled, profiler,
format_satoshis, format_fee_satoshis, NoDynamicFeeEstimates, format_satoshis, format_fee_satoshis, NoDynamicFeeEstimates,
WalletFileException, BitcoinException, WalletFileException, BitcoinException,
InvalidPassword, format_time, timestamp_to_datetime, Satoshis, InvalidPassword, format_time, timestamp_to_datetime, Satoshis,
Fiat, bfh, bh2u, TxMinedInfo, print_error) Fiat, bfh, bh2u, TxMinedInfo)
from .bitcoin import (COIN, TYPE_ADDRESS, is_address, address_to_script, from .bitcoin import (COIN, TYPE_ADDRESS, is_address, address_to_script,
is_minikey, relayfee, dust_threshold) is_minikey, relayfee, dust_threshold)
from .crypto import sha256d from .crypto import sha256d
@ -64,12 +64,15 @@ from .contacts import Contacts
from .interface import RequestTimedOut from .interface import RequestTimedOut
from .ecc_fast import is_using_fast_ecc from .ecc_fast import is_using_fast_ecc
from .mnemonic import Mnemonic from .mnemonic import Mnemonic
from .logging import get_logger
if TYPE_CHECKING: if TYPE_CHECKING:
from .network import Network from .network import Network
from .simple_config import SimpleConfig from .simple_config import SimpleConfig
_logger = get_logger(__name__)
TX_STATUS = [ TX_STATUS = [
_('Unconfirmed'), _('Unconfirmed'),
_('Unconfirmed parent'), _('Unconfirmed parent'),
@ -200,7 +203,6 @@ class Abstract_Wallet(AddressSynchronizer):
max_change_outputs = 3 max_change_outputs = 3
gap_limit_for_change = 6 gap_limit_for_change = 6
verbosity_filter = 'w'
def __init__(self, storage: WalletStorage): def __init__(self, storage: WalletStorage):
if storage.requires_upgrade(): if storage.requires_upgrade():
@ -825,9 +827,9 @@ class Abstract_Wallet(AddressSynchronizer):
# wait until we are connected, because the user # wait until we are connected, because the user
# might have selected another server # might have selected another server
if self.network: if self.network:
self.print_error("waiting for network...") self.logger.info("waiting for network...")
wait_for_network() wait_for_network()
self.print_error("waiting while wallet is syncing...") self.logger.info("waiting while wallet is syncing...")
wait_for_wallet() wait_for_wallet()
else: else:
self.synchronize() self.synchronize()
@ -940,7 +942,7 @@ class Abstract_Wallet(AddressSynchronizer):
raw_tx = self.network.run_from_another_thread( raw_tx = self.network.run_from_another_thread(
self.network.get_transaction(tx_hash, timeout=10)) self.network.get_transaction(tx_hash, timeout=10))
except RequestTimedOut as e: except RequestTimedOut as e:
self.print_error(f'getting input txn from network timed out for {tx_hash}') self.logger.info(f'getting input txn from network timed out for {tx_hash}')
if not ignore_timeout: if not ignore_timeout:
raise e raise e
else: else:
@ -1066,7 +1068,7 @@ class Abstract_Wallet(AddressSynchronizer):
try: try:
baseurl = baseurl.replace(*rewrite) baseurl = baseurl.replace(*rewrite)
except BaseException as e: except BaseException as e:
self.print_stderr('Invalid config setting for "url_rewrite". err:', e) self.logger.info(f'Invalid config setting for "url_rewrite". err: {e}')
out['request_url'] = os.path.join(baseurl, 'req', key[0], key[1], key, key) out['request_url'] = os.path.join(baseurl, 'req', key[0], key[1], key, key)
out['URI'] += '&r=' + out['request_url'] out['URI'] += '&r=' + out['request_url']
out['index_url'] = os.path.join(baseurl, 'index.html') + '?id=' + key out['index_url'] = os.path.join(baseurl, 'index.html') + '?id=' + key
@ -1569,7 +1571,7 @@ class Deterministic_Wallet(Abstract_Wallet):
@profiler @profiler
def try_detecting_internal_addresses_corruption(self): def try_detecting_internal_addresses_corruption(self):
if not is_using_fast_ecc(): if not is_using_fast_ecc():
self.print_error("internal address corruption test skipped due to missing libsecp256k1") self.logger.info("internal address corruption test skipped due to missing libsecp256k1")
return return
addresses_all = self.get_addresses() addresses_all = self.get_addresses()
# sample 1: first few # sample 1: first few
@ -1929,7 +1931,7 @@ def restore_wallet_from_text(text, *, path, network, passphrase=None, password=N
if network: if network:
wallet.start_network(network) wallet.start_network(network)
print_error("Recovering wallet...") _logger.info("Recovering wallet...")
wallet.wait_until_synchronized() wallet.wait_until_synchronized()
wallet.stop_threads() wallet.stop_threads()
# note: we don't wait for SPV # note: we don't wait for SPV

18
electrum/websockets.py

@ -36,9 +36,9 @@ try:
except ImportError: except ImportError:
sys.exit("install SimpleWebSocketServer") sys.exit("install SimpleWebSocketServer")
from .util import PrintError
from . import bitcoin from . import bitcoin
from .synchronizer import SynchronizerBase from .synchronizer import SynchronizerBase
from .logging import Logger
if TYPE_CHECKING: if TYPE_CHECKING:
from .network import Network from .network import Network
@ -48,20 +48,24 @@ if TYPE_CHECKING:
request_queue = asyncio.Queue() request_queue = asyncio.Queue()
class ElectrumWebSocket(WebSocket, PrintError): class ElectrumWebSocket(WebSocket, Logger):
def __init__(self):
WebSocket.__init__(self)
Logger.__init__(self)
def handleMessage(self): def handleMessage(self):
assert self.data[0:3] == 'id:' assert self.data[0:3] == 'id:'
self.print_error("message received", self.data) self.logger.info(f"message received {self.data}")
request_id = self.data[3:] request_id = self.data[3:]
asyncio.run_coroutine_threadsafe( asyncio.run_coroutine_threadsafe(
request_queue.put((self, request_id)), asyncio.get_event_loop()) request_queue.put((self, request_id)), asyncio.get_event_loop())
def handleConnected(self): def handleConnected(self):
self.print_error("connected", self.address) self.logger.info(f"connected {self.address}")
def handleClose(self): def handleClose(self):
self.print_error("closed", self.address) self.logger.info(f"closed {self.address}")
class BalanceMonitor(SynchronizerBase): class BalanceMonitor(SynchronizerBase):
@ -92,13 +96,13 @@ class BalanceMonitor(SynchronizerBase):
try: try:
addr, amount = self.make_request(request_id) addr, amount = self.make_request(request_id)
except Exception: except Exception:
traceback.print_exc(file=sys.stderr) self.logger.exception('')
continue continue
self.expected_payments[addr].append((ws, amount)) self.expected_payments[addr].append((ws, amount))
await self._add_address(addr) await self._add_address(addr)
async def _on_address_status(self, addr, status): async def _on_address_status(self, addr, status):
self.print_error('new status for addr {}'.format(addr)) self.logger.info(f'new status for addr {addr}')
sh = bitcoin.address_to_scripthash(addr) sh = bitcoin.address_to_scripthash(addr)
balance = await self.network.get_balance_for_scripthash(sh) balance = await self.network.get_balance_for_scripthash(sh)
for ws, amount in self.expected_payments[addr]: for ws, amount in self.expected_payments[addr]:

7
electrum/x509.py

@ -31,6 +31,10 @@ import ecdsa
from . import util from . import util
from .util import profiler, bh2u from .util import profiler, bh2u
from .logging import get_logger
_logger = get_logger(__name__)
# algo OIDs # algo OIDs
@ -328,7 +332,7 @@ def load_certificates(ca_path):
except BaseException as e: except BaseException as e:
# with open('/tmp/tmp.txt', 'w') as f: # with open('/tmp/tmp.txt', 'w') as f:
# f.write(pem.pem(b, 'CERTIFICATE').decode('ascii')) # f.write(pem.pem(b, 'CERTIFICATE').decode('ascii'))
util.print_error("cert error:", e) _logger.info(f"cert error: {e}")
continue continue
fp = x.getFingerprint() fp = x.getFingerprint()
@ -341,6 +345,5 @@ def load_certificates(ca_path):
if __name__ == "__main__": if __name__ == "__main__":
import certifi import certifi
util.set_verbosity(True)
ca_path = certifi.where() ca_path = certifi.where()
ca_list, ca_keyID = load_certificates(ca_path) ca_list, ca_keyID = load_certificates(ca_path)

22
run_electrum

@ -76,17 +76,21 @@ if not is_android:
check_imports() check_imports()
from electrum.logging import get_logger, configure_logging
from electrum import util from electrum import util
from electrum import constants from electrum import constants
from electrum import SimpleConfig from electrum import SimpleConfig
from electrum.wallet import Wallet from electrum.wallet import Wallet
from electrum.storage import WalletStorage, get_derivation_used_for_hw_device_encryption from electrum.storage import WalletStorage, get_derivation_used_for_hw_device_encryption
from electrum.util import print_msg, print_stderr, json_encode, json_decode, UserCancelled from electrum.util import print_msg, print_stderr, json_encode, json_decode, UserCancelled
from electrum.util import set_verbosity, InvalidPassword from electrum.util import InvalidPassword
from electrum.commands import get_parser, known_commands, Commands, config_variables from electrum.commands import get_parser, known_commands, Commands, config_variables
from electrum import daemon from electrum import daemon
from electrum import keystore from electrum import keystore
_logger = get_logger(__name__)
# get password routine # get password routine
def prompt_password(prompt, confirm=True): def prompt_password(prompt, confirm=True):
import getpass import getpass
@ -184,12 +188,12 @@ def get_connected_hw_devices(plugins):
name, plugin = splugin.name, splugin.plugin name, plugin = splugin.name, splugin.plugin
if not plugin: if not plugin:
e = splugin.exception e = splugin.exception
print_stderr(f"{name}: error during plugin init: {repr(e)}") _logger.error(f"{name}: error during plugin init: {repr(e)}")
continue continue
try: try:
u = devmgr.unpaired_device_infos(None, plugin) u = devmgr.unpaired_device_infos(None, plugin)
except: except Exception as e:
devmgr.print_error(f'error getting device infos for {name}: {e}') _logger.error(f'error getting device infos for {name}: {repr(e)}')
continue continue
devices += list(map(lambda x: (name, x), u)) devices += list(map(lambda x: (name, x), u))
return devices return devices
@ -273,6 +277,9 @@ if __name__ == '__main__':
sys.argv.append('-h') sys.argv.append('-h')
# old '-v' syntax # old '-v' syntax
# Due to this workaround that keeps old -v working,
# more advanced usages of -v need to use '-v='.
# e.g. -v=debug,network=warning,interface=error
try: try:
i = sys.argv.index('-v') i = sys.argv.index('-v')
except ValueError: except ValueError:
@ -320,10 +327,7 @@ if __name__ == '__main__':
if config_options.get('portable'): if config_options.get('portable'):
config_options['electrum_path'] = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'electrum_data') config_options['electrum_path'] = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'electrum_data')
# kivy sometimes freezes when we write to sys.stderr if not config_options.get('verbosity'):
log_verbosity = config_options.get('verbosity') if config_options.get('gui') != 'kivy' else ''
set_verbosity(log_verbosity)
if not log_verbosity:
warnings.simplefilter('ignore', DeprecationWarning) warnings.simplefilter('ignore', DeprecationWarning)
# check uri # check uri
@ -336,6 +340,8 @@ if __name__ == '__main__':
# todo: defer this to gui # todo: defer this to gui
config = SimpleConfig(config_options) config = SimpleConfig(config_options)
configure_logging(config)
cmdname = config.get('cmd') cmdname = config.get('cmd')
if config.get('testnet'): if config.get('testnet'):

Loading…
Cancel
Save