You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
171 lines
5.9 KiB
171 lines
5.9 KiB
# -*- coding: utf-8 -*- |
|
|
|
import asyncio |
|
import pathlib |
|
import logging |
|
import threading |
|
|
|
from electrum import constants, util |
|
from electrum.i18n import _ |
|
from electrum.logging import Logger |
|
|
|
from .jm_conf import JMConf |
|
from .jm_wallet import JMWallet |
|
from .jm_util import JMStates, JMGUILogHandler |
|
from .jmclient import get_tumble_log |
|
|
|
|
|
class JMManager(Logger): |
|
'''Class representing JoinMarket manager''' |
|
|
|
LOGGING_SHORTCUT = 'J' |
|
|
|
def __init__(self, wallet): |
|
self.debug = False |
|
self.wallet = wallet |
|
self.network = None |
|
self.loop = None |
|
self.config = config = wallet.config |
|
Logger.__init__(self) |
|
self.log_handler = JMGUILogHandler(self) |
|
self.logger = logging.LoggerAdapter(self.logger, |
|
{'jmman_id': id(self)}) |
|
# tumble log will not always be used, but is made available anyway: |
|
self.tumble_logsdir = logsdir = pathlib.Path(config.path) / "logs" |
|
self.tumble_log = get_tumble_log(self, logsdir, config) |
|
|
|
self._state = JMStates.Unsupported |
|
self.jmw = JMWallet(self) |
|
self.jmconf = JMConf(self) |
|
self.jmw.jmconf = self.jmconf |
|
|
|
self.states = JMStates |
|
self.state_lock = threading.Lock() |
|
|
|
self.postponed_notifications = {} |
|
|
|
self.wallet_types_supported = ['standard'] |
|
self.keystore_types_supported = ['bip32'] |
|
keystore = wallet.db.get('keystore') |
|
if keystore: |
|
self.w_ks_type = keystore.get('type', 'unknown') |
|
else: |
|
self.w_ks_type = 'unknown' |
|
self.w_type = wallet.wallet_type |
|
if (self.w_type in self.wallet_types_supported |
|
and self.w_ks_type in self.keystore_types_supported): |
|
if constants.net.TESTNET: |
|
jm_data = self.wallet.db.get('jm_data') |
|
if jm_data and jm_data.get('jm_enabled', False): |
|
self._state = JMStates.Ready |
|
self.jmw.init_jm_data() |
|
self.jmconf.init_max_mixdepth() |
|
else: |
|
self._state = JMStates.Disabled |
|
if self.unsupported: |
|
supported_w = ', '.join(self.wallet_types_supported) |
|
supported_ks = ', '.join(self.keystore_types_supported) |
|
this_type = self.w_type |
|
this_ks_type = self.w_ks_type |
|
if not constants.net.TESTNET: |
|
self.unsupported_msg = _( |
|
'JoinMarket is currently not supported on mainnet') |
|
else: |
|
self.unsupported_msg = _( |
|
f'JoinMarket plugin is currently supported on' |
|
f' next wallet types: "{supported_w}"' |
|
f' and keystore types: "{supported_ks}".' |
|
f'\n\nThis wallet has type "{this_type}"' |
|
f' and kestore type "{this_ks_type}".') |
|
else: |
|
self.unsupported_msg = '' |
|
self.client_factory = None |
|
|
|
def diagnostic_name(self): |
|
return str(self.wallet) if self.wallet else '' |
|
|
|
def set_client_factory(self, client_factory): |
|
self.client_factory = client_factory |
|
|
|
@property |
|
def state(self): |
|
'''Current state of JMManager (jm_util.JMStates enum)''' |
|
return self._state |
|
|
|
@state.setter |
|
def state(self, state): |
|
'''Set current state of JMManager''' |
|
if not self.enabled: |
|
self.logger.debug('Ignoring JMManager.state change ' |
|
'for disabled JMManager') |
|
return |
|
self._state = state |
|
|
|
@property |
|
def unsupported(self): |
|
'''Wallet and keystore types is not supported''' |
|
return self.state == JMStates.Unsupported |
|
|
|
@property |
|
def enabled(self): |
|
'''JM was enabled on this wallet''' |
|
return self.state not in [JMStates.Unsupported, JMStates.Disabled] |
|
|
|
def enable_jm(self): |
|
'''Enables JM on this wallet, store changes in db.''' |
|
if not self.enabled: |
|
coro = self._enable_jm() |
|
fut = asyncio.run_coroutine_threadsafe(coro, self.loop) |
|
return fut.result() |
|
else: |
|
return False |
|
|
|
async def _enable_jm(self): |
|
'''Start initialization, find untracked txs''' |
|
if self.enabled: |
|
return False |
|
self._state = JMStates.Ready |
|
self.jmw.init_jm_data() |
|
self.jmconf.init_max_mixdepth() |
|
self.jmw.set_jm_data('jm_enabled', True) |
|
await self.loop.run_in_executor(None, self.jmw.load_and_cleanup) |
|
self.wallet.save_db() |
|
self.logger.info('JMManager initialized') |
|
return True |
|
|
|
@property |
|
def is_mixing(self): |
|
return self.state == JMStates.Mixing |
|
|
|
def need_password(self): |
|
'''Check if password is needed to sign wallet transactions.''' |
|
return self.wallet.has_password() |
|
|
|
def on_network_start(self, network): |
|
'''Run when network is connected to the wallet''' |
|
self.network = network |
|
self.loop = network.asyncio_loop |
|
self.jmw.network = network |
|
self.jmw.loop = self.loop |
|
self.jmw.register_callbacks() |
|
self.jmw.on_network_start(network) |
|
asyncio.ensure_future(self.trigger_postponed_notifications()) |
|
|
|
def on_stop_threads(self): |
|
'''Run when the wallet is unloaded/stopped''' |
|
self.jmw.unregister_callbacks() |
|
|
|
def postpone_notification(self, event, *args): |
|
'''Postpone notification to send many analogous notifications as one''' |
|
self.postponed_notifications[event] = args |
|
|
|
async def trigger_postponed_notifications(self): |
|
'''Trigger postponed notification''' |
|
while True: |
|
await asyncio.sleep(0.5) |
|
for event in list(self.postponed_notifications.keys()): |
|
args = self.postponed_notifications.pop(event, tuple()) |
|
try: |
|
util.trigger_callback(event, *args) |
|
except Exception as e: |
|
self.logger.warning(f'trigger_callback: {repr(e)}')
|
|
|