swaps: add base class for transports. move "get_recent_offers" logic
the "get_recent_offers" logic is now shared between the GUIs
This commit is contained in:
@@ -12,7 +12,7 @@ from electrum.bitcoin import DummyAddress
|
||||
from electrum.logging import get_logger
|
||||
from electrum.transaction import PartialTxOutput, PartialTransaction
|
||||
from electrum.util import NotEnoughFunds, NoDynamicFeeEstimates, profiler, get_asyncio_loop, age
|
||||
from electrum.submarine_swaps import NostrTransport
|
||||
from electrum.submarine_swaps import NostrTransport, SwapServerTransport
|
||||
|
||||
from electrum.gui import messages
|
||||
|
||||
@@ -354,7 +354,7 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener):
|
||||
|
||||
swap_transport = swap_manager.create_transport()
|
||||
|
||||
def query_task(transport):
|
||||
def query_task(transport: SwapServerTransport):
|
||||
with transport:
|
||||
try:
|
||||
async def wait_initialized():
|
||||
@@ -388,7 +388,7 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener):
|
||||
])
|
||||
self.state = QESwapHelper.State.NoService
|
||||
return
|
||||
self.recent_offers = [x for x in transport.offers.values()]
|
||||
self.recent_offers = [x for x in transport.get_recent_offers()]
|
||||
if not self.recent_offers:
|
||||
self.userinfo = _('Could not find a swap provider.')
|
||||
self.state = QESwapHelper.State.NoService
|
||||
@@ -398,7 +398,7 @@ class QESwapHelper(AuthMixin, QObject, QtEventListener):
|
||||
self.undefinedNPub.emit()
|
||||
return
|
||||
else:
|
||||
self.recent_offers = [x for x in transport.offers.values()]
|
||||
self.recent_offers = [x for x in transport.get_recent_offers()]
|
||||
if not self.recent_offers:
|
||||
self.userinfo = _('Could not find a swap provider.')
|
||||
self.state = QESwapHelper.State.NoService
|
||||
|
||||
@@ -71,6 +71,7 @@ from electrum.simple_config import SimpleConfig
|
||||
from electrum.logging import Logger
|
||||
from electrum.lntransport import extract_nodeid, ConnStringFormatError
|
||||
from electrum.lnaddr import lndecode
|
||||
from electrum.submarine_swaps import SwapServerTransport, NostrTransport
|
||||
|
||||
from .rate_limiter import rate_limited
|
||||
from .exception_window import Exception_Hook
|
||||
@@ -1187,25 +1188,25 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
except UserCancelled:
|
||||
return
|
||||
|
||||
def create_sm_transport(self):
|
||||
def create_sm_transport(self) -> Optional['SwapServerTransport']:
|
||||
sm = self.wallet.lnworker.swap_manager
|
||||
if sm.is_server:
|
||||
self.show_error(_('Swap server is active'))
|
||||
return False
|
||||
return None
|
||||
|
||||
if self.network is None:
|
||||
return False
|
||||
return None
|
||||
|
||||
if not self.config.SWAPSERVER_URL and not self.config.SWAPSERVER_NPUB:
|
||||
if not self.question('\n'.join([
|
||||
_('Electrum uses Nostr in order to find liquidity providers.'),
|
||||
_('Do you want to enable Nostr?'),
|
||||
])):
|
||||
return False
|
||||
return None
|
||||
|
||||
return sm.create_transport()
|
||||
|
||||
def initialize_swap_manager(self, transport):
|
||||
def initialize_swap_manager(self, transport: 'SwapServerTransport'):
|
||||
sm = self.wallet.lnworker.swap_manager
|
||||
if not sm.is_initialized.is_set():
|
||||
async def wait_until_initialized():
|
||||
@@ -1226,7 +1227,8 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
assert sm.is_initialized.is_set()
|
||||
return True
|
||||
|
||||
def choose_swapserver_dialog(self, transport):
|
||||
def choose_swapserver_dialog(self, transport: NostrTransport) -> bool:
|
||||
assert isinstance(transport, NostrTransport)
|
||||
if not transport.is_connected.is_set():
|
||||
self.show_message(
|
||||
'\n'.join([
|
||||
@@ -1234,8 +1236,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
_('Please check your relays and network connection'),
|
||||
]))
|
||||
return False
|
||||
now = int(time.time())
|
||||
recent_offers = [x for x in transport.offers.values() if now - x['timestamp'] < 3600]
|
||||
recent_offers = transport.get_recent_offers()
|
||||
if not recent_offers:
|
||||
self.show_message(
|
||||
'\n'.join([
|
||||
@@ -1245,9 +1246,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
sm = self.wallet.lnworker.swap_manager
|
||||
def descr(x):
|
||||
last_seen = util.age(x['timestamp'])
|
||||
return f"pubkey={x['pubkey'][0:10]}, fee={x['percentage_fee']}% + {x['reverse_mining_fee']} sats"
|
||||
pow_sorted_offers = sorted(recent_offers, key=lambda x: x['pow_bits'], reverse=True)
|
||||
server_keys = [(x['pubkey'], descr(x)) for x in pow_sorted_offers]
|
||||
return (f"pubkey={x['pubkey'][0:10]}, "
|
||||
f"fee={x['percentage_fee']}% + {x['reverse_mining_fee']} sats, "
|
||||
f"last_seen: {last_seen}")
|
||||
server_keys = [(x['pubkey'], descr(x)) for x in recent_offers]
|
||||
msg = '\n'.join([
|
||||
_("Please choose a server from this list."),
|
||||
_("Note that fees may be updated frequently.")
|
||||
@@ -1258,7 +1260,7 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger, QtEventListener):
|
||||
title = _("Choose Swap Server"),
|
||||
default_choice = self.config.SWAPSERVER_NPUB
|
||||
)
|
||||
if choice not in transport.offers:
|
||||
if choice is None:
|
||||
return False
|
||||
self.config.SWAPSERVER_NPUB = choice
|
||||
pairs = transport.get_offer(choice)
|
||||
|
||||
@@ -19,6 +19,7 @@ from .my_treeview import create_toolbar_with_menu
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .main_window import ElectrumWindow
|
||||
from electrum.submarine_swaps import SwapServerTransport
|
||||
|
||||
CANNOT_RECEIVE_WARNING = _(
|
||||
"""The requested amount is higher than what you can receive in your currently open channels.
|
||||
@@ -33,7 +34,7 @@ class InvalidSwapParameters(Exception): pass
|
||||
|
||||
class SwapDialog(WindowModalDialog, QtEventListener):
|
||||
|
||||
def __init__(self, window: 'ElectrumWindow', transport, is_reverse=None, recv_amount_sat=None, channels=None):
|
||||
def __init__(self, window: 'ElectrumWindow', transport: 'SwapServerTransport', is_reverse=None, recv_amount_sat=None, channels=None):
|
||||
WindowModalDialog.__init__(self, window, _('Submarine Swap'))
|
||||
self.window = window
|
||||
self.config = window.config
|
||||
|
||||
@@ -261,7 +261,7 @@ class SwapManager(Logger):
|
||||
async def stop(self):
|
||||
await self.taskgroup.cancel_remaining()
|
||||
|
||||
def create_transport(self):
|
||||
def create_transport(self) -> 'SwapServerTransport':
|
||||
from .lnutil import generate_random_keypair
|
||||
if self.config.SWAPSERVER_URL:
|
||||
return HttpTransport(self.config, self)
|
||||
@@ -1266,16 +1266,33 @@ class SwapManager(Logger):
|
||||
return swap.funding_txid
|
||||
|
||||
|
||||
class SwapServerTransport(Logger):
|
||||
|
||||
class HttpTransport(Logger):
|
||||
|
||||
def __init__(self, config, sm):
|
||||
def __init__(self, *, config: 'SimpleConfig', sm: 'SwapManager'):
|
||||
Logger.__init__(self)
|
||||
self.sm = sm
|
||||
self.network = sm.network
|
||||
self.api_url = config.SWAPSERVER_URL
|
||||
self.config = config
|
||||
self.is_connected = asyncio.Event()
|
||||
|
||||
def __enter__(self):
|
||||
pass
|
||||
|
||||
def __exit__(self, ex_type, ex, tb):
|
||||
pass
|
||||
|
||||
async def send_request_to_server(self, method: str, request_data: Optional[dict]) -> dict:
|
||||
pass
|
||||
|
||||
async def get_pairs(self) -> None:
|
||||
pass
|
||||
|
||||
|
||||
class HttpTransport(SwapServerTransport):
|
||||
|
||||
def __init__(self, config, sm):
|
||||
SwapServerTransport.__init__(self, config=config, sm=sm)
|
||||
self.api_url = config.SWAPSERVER_URL
|
||||
self.is_connected.set()
|
||||
|
||||
def __enter__(self):
|
||||
@@ -1314,7 +1331,7 @@ class HttpTransport(Logger):
|
||||
self.sm.update_pairs(pairs)
|
||||
|
||||
|
||||
class NostrTransport(Logger):
|
||||
class NostrTransport(SwapServerTransport):
|
||||
# uses nostr:
|
||||
# - to advertise servers
|
||||
# - for client-server RPCs (using DMs)
|
||||
@@ -1326,11 +1343,8 @@ class NostrTransport(Logger):
|
||||
OFFER_UPDATE_INTERVAL_SEC = 60 * 10
|
||||
|
||||
def __init__(self, config, sm, keypair):
|
||||
Logger.__init__(self)
|
||||
self.config = config
|
||||
self.network = sm.network
|
||||
self.sm = sm
|
||||
self.offers = {}
|
||||
SwapServerTransport.__init__(self, config=config, sm=sm)
|
||||
self._offers = {} # type: Dict[str, Dict]
|
||||
self.private_key = keypair.privkey
|
||||
self.nostr_private_key = to_nip19('nsec', keypair.privkey.hex())
|
||||
self.nostr_pubkey = keypair.pubkey.hex()[2:]
|
||||
@@ -1338,7 +1352,6 @@ class NostrTransport(Logger):
|
||||
ssl_context = ssl.create_default_context(purpose=ssl.Purpose.SERVER_AUTH, cafile=ca_path)
|
||||
self.relay_manager = aionostr.Manager(self.relays, private_key=self.nostr_private_key, log=self.logger, ssl_context=ssl_context)
|
||||
self.taskgroup = OldTaskGroup()
|
||||
self.is_connected = asyncio.Event()
|
||||
self.server_relays = None
|
||||
|
||||
def __enter__(self):
|
||||
@@ -1390,9 +1403,19 @@ class NostrTransport(Logger):
|
||||
return self.network.config.NOSTR_RELAYS.split(',')
|
||||
|
||||
def get_offer(self, pubkey):
|
||||
offer = self.offers.get(pubkey)
|
||||
offer = self._offers.get(pubkey)
|
||||
return self._parse_offer(offer)
|
||||
|
||||
def get_recent_offers(self) -> Sequence[Dict]:
|
||||
# filter to fresh timestamps
|
||||
now = int(time.time())
|
||||
recent_offers = [x for x in self._offers.values() if now - x['timestamp'] < 3600]
|
||||
# sort by proof-of-work
|
||||
recent_offers = sorted(recent_offers, key=lambda x: x['pow_bits'], reverse=True)
|
||||
# cap list size
|
||||
recent_offers = recent_offers[:20]
|
||||
return recent_offers
|
||||
|
||||
def _parse_offer(self, offer):
|
||||
return SwapFees(
|
||||
percentage = offer['percentage_fee'],
|
||||
@@ -1439,11 +1462,11 @@ class NostrTransport(Logger):
|
||||
return event_id
|
||||
|
||||
@log_exceptions
|
||||
async def send_request_to_server(self, method: str, request: dict) -> dict:
|
||||
request['method'] = method
|
||||
request['relays'] = self.config.NOSTR_RELAYS
|
||||
async def send_request_to_server(self, method: str, request_data: dict) -> dict:
|
||||
request_data['method'] = method
|
||||
request_data['relays'] = self.config.NOSTR_RELAYS
|
||||
server_pubkey = self.config.SWAPSERVER_NPUB
|
||||
event_id = await self.send_direct_message(server_pubkey, self.server_relays, json.dumps(request))
|
||||
event_id = await self.send_direct_message(server_pubkey, self.server_relays, json.dumps(request_data))
|
||||
response = await self.dm_replies[event_id]
|
||||
return response
|
||||
|
||||
@@ -1468,7 +1491,7 @@ class NostrTransport(Logger):
|
||||
continue
|
||||
# check if this is the most recent event for this pubkey
|
||||
pubkey = event.pubkey
|
||||
ts = self.offers.get(pubkey, {}).get('timestamp', 0)
|
||||
ts = self._offers.get(pubkey, {}).get('timestamp', 0)
|
||||
if event.created_at <= ts:
|
||||
#print('skipping old event', pubkey[0:10], event.id)
|
||||
continue
|
||||
@@ -1485,7 +1508,7 @@ class NostrTransport(Logger):
|
||||
content['pow_bits'] = pow_bits
|
||||
content['pubkey'] = pubkey
|
||||
content['timestamp'] = event.created_at
|
||||
self.offers[pubkey] = content
|
||||
self._offers[pubkey] = content
|
||||
# mirror event to other relays
|
||||
server_relays = content['relays'].split(',') if 'relays' in content else []
|
||||
await self.taskgroup.spawn(self.rebroadcast_event(event, server_relays))
|
||||
|
||||
Reference in New Issue
Block a user