diff --git a/basicswap/basicswap.py b/basicswap/basicswap.py index 61e6aa7..0091534 100644 --- a/basicswap/basicswap.py +++ b/basicswap/basicswap.py @@ -2296,7 +2296,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp): offer_bytes = msg_buf.to_bytes() payload_hex = str.format("{:02x}", MessageTypes.OFFER) + offer_bytes.hex() msg_valid: int = max(self.SMSG_SECONDS_IN_HOUR, valid_for_seconds) - # Send offers to active and bridged networks, message_nets contains only the active networks. + # Send offers to active and bridged networks offer_id = self.sendMessage( offer_addr, offer_addr_to, payload_hex, msg_valid, cursor ) @@ -3277,8 +3277,11 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp): "amount_from": amount, "amount_to": amount_to, } + bid_message_nets = self.selectMessageNetStringForConcept( + Concepts.OFFER, offer_id, offer.message_nets, cursor + ) route_id, route_established = self.prepareMessageRoute( - MessageNetworks.SIMPLEX, + bid_message_nets, request_data, bid_addr, offer.addr_from, @@ -3291,10 +3294,6 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp): dt.datetime.fromtimestamp(now).date(), contract_count ) - bid_message_nets = self.selectMessageNetStringForConcept( - Concepts.OFFER, offer_id, offer.message_nets, cursor - ) - bid = Bid( protocol_version=PROTOCOL_VERSION_SECRET_HASH, active_ind=1, @@ -3905,7 +3904,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp): def prepareMessageRoute( self, - network_id, + message_nets, req_data, addr_from: str, addr_to: str, @@ -3915,9 +3914,34 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp): if self._use_direct_message_routes is False: return None, False + # message_nets contains the network selected to use: + # TODO: allow multiple networks + self.logD(LC.NET, f"prepareMessageRoute message_nets {message_nets}") + + if message_nets.startswith("b."): + # TODO: get direct messages working through portals + self.logD(LC.NET, "Not using route - bridged networks.") + return None, False + if len(message_nets) == 0: + if len(self.active_networks) == 1: + network_id: int = networkTypeToID( + self.active_networks[0].get("type", "smsg") + ) + else: + raise RuntimeError( + "Network must be specified if multiple networks are active." + ) + else: + network_id: int = networkTypeToID(message_nets) + if network_id not in (MessageNetworks.SIMPLEX,): + return None, False try: - net_i = self.getActiveNetworkInterface(2) + net_i = self.getActiveNetworkInterface(network_id) except Exception as e: # noqa: F841 + self.logD( + LC.NET, + f"Not using route - network interface not found for {network_id}.", + ) return None, False # Look for active route @@ -3950,13 +3974,18 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp): msg_valid: int = max(self.SMSG_SECONDS_IN_HOUR, valid_for_seconds) connect_req_msgid = self.sendMessage( - addr_from, addr_to, payload_hex, msg_valid, cursor + addr_from, + addr_to, + payload_hex, + msg_valid, + cursor, + message_nets=message_nets, ) now: int = self.getTime() message_route = DirectMessageRoute( active_ind=2, - network_id=2, + network_id=network_id, linked_type=Concepts.OFFER, smsg_addr_local=addr_from, smsg_addr_remote=addr_to, @@ -4034,8 +4063,11 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp): "amount_from": amount, "amount_to": amount_to, } + bid_message_nets = self.selectMessageNetStringForConcept( + Concepts.OFFER, offer.offer_id, offer.message_nets, cursor + ) route_id, route_established = self.prepareMessageRoute( - MessageNetworks.SIMPLEX, + bid_message_nets, request_data, bid_addr, offer.addr_from, @@ -4043,9 +4075,6 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp): valid_for_seconds, ) - bid_message_nets = self.selectMessageNetStringForConcept( - Concepts.OFFER, offer.offer_id, offer.message_nets, cursor - ) reverse_bid: bool = self.is_reverse_ads_bid(coin_from, coin_to) if reverse_bid: reversed_rate: int = ci_to.make_int(amount / amount_to, r=1) @@ -7554,7 +7583,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp): self.log.debug("Ignoring expired offer.") return - _ = self.expandMessageNets(offer_data.message_nets) # Decode to validate + self.validateMessageNets(offer_data.message_nets) offer_rate: int = ci_from.make_int( offer_data.amount_to / offer_data.amount_from, r=1 @@ -7997,7 +8026,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp): bid_rate: int = ci_from.make_int(bid_data.amount_to / bid_data.amount, r=1) self.validateBidAmount(offer, bid_data.amount, bid_rate) - _ = self.expandMessageNets(bid_data.message_nets) # Decode to validate + self.validateMessageNets(bid_data.message_nets) network_type: str = msg.get("msg_net", "smsg") network_type_received_on_id: int = networkTypeToID(network_type) @@ -8468,7 +8497,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp): if ci_to.curve_type() == Curves.ed25519: ensure(len(bid_data.kbsf_dleag) <= 16000, "Invalid kbsf_dleag size") - _ = self.expandMessageNets(bid_data.message_nets) # Decode to validate + self.validateMessageNets(bid_data.message_nets) bid_id = bytes.fromhex(msg["msgid"]) @@ -10006,7 +10035,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp): ensure(offer.swap_type == SwapTypes.XMR_SWAP, "Bid/offer swap type mismatch") ensure(xmr_offer, f"Adaptor-sig offer not found: {self.log.id(offer_id)}.") - _ = self.expandMessageNets(bid_data.message_nets) # Decode to validate + self.validateMessageNets(bid_data.message_nets) ci_from = self.ci(offer.coin_to) ci_to = self.ci(offer.coin_from) @@ -10027,7 +10056,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp): ) self.validateBidAmount(offer, bid_data.amount_from, bid_rate) - _ = self.expandMessageNets(bid_data.message_nets) + _, _ = self.expandMessageNets(bid_data.message_nets) bid_id = bytes.fromhex(msg["msgid"]) @@ -10197,7 +10226,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp): offer_id = bytes.fromhex(req_data["offer_id"]) bidder_addr = req_data["bsx_address"] - net_i = self.getActiveNetworkInterface(2) + net_i = self.getActiveNetworkInterface(MessageNetworks.SIMPLEX) try: cursor = self.openDB() offer = self.getOffer(offer_id, cursor) diff --git a/basicswap/db.py b/basicswap/db.py index aade15b..8b9ca96 100644 --- a/basicswap/db.py +++ b/basicswap/db.py @@ -724,7 +724,7 @@ class DirectMessageRouteLink(Table): class NetworkPortal(Table): __tablename__ = "network_portals" - def __init__( + def set( self, time_start, time_valid, network_from, network_to, address_from, address_to ): super().__init__() diff --git a/basicswap/network/bsx_network.py b/basicswap/network/bsx_network.py index b1c2e06..4b9023e 100644 --- a/basicswap/network/bsx_network.py +++ b/basicswap/network/bsx_network.py @@ -50,15 +50,18 @@ def networkTypeToID(type: str) -> int: return MessageNetworks.SMSG elif type == "simplex": return MessageNetworks.SIMPLEX - raise RuntimeError(f"Unknown message type: {type}") + raise RuntimeError(f"Unknown message network type: {type}") -def networkIDToType(id: int) -> str: +def networkIDToType(id: int, bridged: bool = False) -> str: + network_name = None if id == MessageNetworks.SMSG: - return "smsg" + network_name = "smsg" elif id == MessageNetworks.SIMPLEX: - return "simplex" - raise RuntimeError(f"Unknown message network id: {id}") + network_name = "simplex" + else: + raise RuntimeError(f"Unknown message network id: {id}") + return ("b." if bridged else "") + network_name class BSXNetwork: @@ -350,7 +353,11 @@ class BSXNetwork: for bridged_network in network.get("bridged", []): bridged_network_type = bridged_network.get("type", "smsg") bridged_networks_set.add(bridged_network_type) - all_networks = active_networks_set | bridged_networks_set + all_networks = active_networks_set + # Join without duplicates, bridged networks are prefixed with "b." + for bridged_network in bridged_networks_set: + if bridged_network not in active_networks_set: + all_networks.add("b." + bridged_network) return ",".join(all_networks) def selectMessageNetString( @@ -366,26 +373,42 @@ class BSXNetwork: for bridged_network in network.get("bridged", []): bridged_network_type = bridged_network.get("type", "smsg") bridged_networks_set.add(networkTypeToID(bridged_network_type)) - remote_network_ids = self.expandMessageNets(remote_message_nets) + remote_active_network_ids, remote_bridged_network_ids = self.expandMessageNets( + remote_message_nets + ) - if len(remote_network_ids) < 1 and len(received_on_network_ids) < 1: + # If no data was sent it must be from an old version + if ( + len(remote_active_network_ids) < 1 + and len(remote_bridged_network_ids) < 1 + and len(received_on_network_ids) < 1 + ): return networkIDToType(random.choice(tuple(active_networks_set))) # Choose which network to respond on - # Pick the received on network if it's in the local node's active networks and the list of remote node's networks + # Pick the received on network if it's in the local node's active networks and the list of remote node's active networks # else prefer a network the local node has active for received_on_id in received_on_network_ids: if ( received_on_id in active_networks_set - and received_on_id in remote_network_ids + and received_on_id in remote_active_network_ids ): return networkIDToType(received_on_id) + # Prefer to use a network both nodes have active for local_net_id in active_networks_set: - if local_net_id in remote_network_ids: + if local_net_id in remote_active_network_ids: return networkIDToType(local_net_id) + # Else prefer to use a network this node has active + for local_net_id in active_networks_set: + if local_net_id in remote_bridged_network_ids: + return networkIDToType(local_net_id, True) + for local_net_id in bridged_networks_set: - if local_net_id in remote_network_ids: - return networkIDToType(local_net_id) + if local_net_id in remote_active_network_ids: + return networkIDToType(local_net_id, True) + for local_net_id in bridged_networks_set: + if local_net_id in remote_bridged_network_ids: + return networkIDToType(local_net_id, True) raise RuntimeError("Unable to select network to respond on") def selectMessageNetStringForConcept( @@ -409,18 +432,30 @@ class BSXNetwork: return self.selectMessageNetString(received_on_network_ids, remote_message_nets) - def expandMessageNets(self, message_nets: str) -> list: + def expandMessageNets(self, message_nets: str) -> (list, list): if message_nets is None or len(message_nets) < 1: - return [] + return [], [] if len(message_nets) > 256: raise ValueError("message_nets string is too large") - rv = [] + active_networks = [] + bridged_networks = [] for network_string in message_nets.split(","): + add_to_list = active_networks + if network_string.startswith("b."): + network_string = network_string[2:] + add_to_list = bridged_networks try: - rv.append(networkTypeToID(network_string)) + network_id: int = networkTypeToID(network_string) except Exception as e: # noqa: F841 self.log.debug(f"Unknown message_net {network_string}") - return rv + if network_id in active_networks or network_id in bridged_networks: + raise ValueError("Malformed networks data.") + add_to_list.append(network_id) + return active_networks, bridged_networks + + def validateMessageNets(self, message_nets: str) -> None: + # Decode to validate + _, _ = self.expandMessageNets(message_nets) def getMessageRoute( self, network_id: int, address_from: str, address_to: str, cursor=None @@ -463,12 +498,14 @@ class BSXNetwork: linked_id=None, timestamp=None, deterministic=False, - message_nets=None, # None -> all, else + message_nets=None, # None|empty -> all ) -> bytes: message_id: bytes = None - networks_list = self.expandMessageNets( + active_networks_list, bridged_networks_list = self.expandMessageNets( message_nets - ) # Empty list means send to all networks + ) + # Empty list means send to all networks + networks_list = active_networks_list + bridged_networks_list networks_sent_to = set() # Message routes work only with simplex messages for now. @@ -478,7 +515,7 @@ class BSXNetwork: message_route = self.getMessageRoute(2, addr_from, addr_to, cursor=cursor) if message_route: - network = self.getActiveNetwork(2) + network = self.getActiveNetwork(MessageNetworks.SIMPLEX) net_i = network["ws_thread"] remote_name = None @@ -699,7 +736,7 @@ class BSXNetwork: self.num_smsg_messages_sent += 1 def processContactDisconnected(self, event_data) -> None: - net_i = self.getActiveNetworkInterface(2) + net_i = self.getActiveNetworkInterface(MessageNetworks.SIMPLEX) connId = getResponseData(event_data, "contact")["activeConn"]["connId"] self.log.info(f"Direct message route disconnected, connId: {connId}") closeSimplexChat(self, net_i, connId) @@ -727,7 +764,7 @@ class BSXNetwork: self.closeDB(cursor) def closeMessageRoute(self, record_id, network_id, route_data, cursor): - net_i = self.getActiveNetworkInterface(2) + net_i = self.getActiveNetworkInterface(MessageNetworks.SIMPLEX) connId = route_data["pccConnId"] @@ -774,7 +811,8 @@ class BSXNetwork: addr_portal: str = self.prepareSMSGAddress( None, AddressTypes.PORTAL_LOCAL, cursor ) - portal = NetworkPortal( + portal = NetworkPortal() + portal.set( now, 30 * 60, network_from_id, network_to_id, addr_portal, addr_to ) portal.created_at = now @@ -971,7 +1009,8 @@ class BSXNetwork: }, ) if received_portal is None: - received_portal = NetworkPortal( + received_portal = NetworkPortal() + received_portal.set( time_start, portal_data.time_valid, portal_data.network_type_from, diff --git a/tests/basicswap/extended/test_multinet.py b/tests/basicswap/extended/test_multinet.py index c0d1d01..895e0ee 100644 --- a/tests/basicswap/extended/test_multinet.py +++ b/tests/basicswap/extended/test_multinet.py @@ -106,8 +106,6 @@ class Test(TestSimplex2): logger.info("---------- Test multinet swap across networks") swap_clients = self.swap_clients - for sc in swap_clients: - sc._use_direct_message_routes = False swap_clients[2]._bridge_networks = True assert len(swap_clients[0].active_networks) == 1 @@ -163,8 +161,6 @@ class Test(TestSimplex2): logger.info("---------- Test reversed swap across networks") swap_clients = self.swap_clients - for sc in swap_clients: - sc._use_direct_message_routes = False swap_clients[2]._bridge_networks = True coin_from = Coins.XMR @@ -214,9 +210,8 @@ class Test(TestSimplex2): logger.info("---------- Test secret hash swap across networks") swap_clients = self.swap_clients - for sc in swap_clients: - sc._use_direct_message_routes = False swap_clients[2]._bridge_networks = True + coin_from = Coins.PART coin_to = Coins.BTC self.prepare_balance(coin_to, 100.0, 1801, 1800) @@ -271,9 +266,6 @@ class Test(TestSimplex2): # Messages for bids should only be sent to one network swap_clients = self.swap_clients - - for sc in swap_clients: - sc._use_direct_message_routes = False swap_clients[2]._bridge_networks = True assert len(swap_clients[2].active_networks) == 2 diff --git a/tests/basicswap/extended/test_simplex.py b/tests/basicswap/extended/test_simplex.py index 20e2183..32227f0 100644 --- a/tests/basicswap/extended/test_simplex.py +++ b/tests/basicswap/extended/test_simplex.py @@ -457,6 +457,7 @@ class TestSimplex2(BaseTest): @classmethod def addCoinSettings(cls, settings, datadir, node_id): + settings["smsg_plaintext_version"] = 2 settings["networks"] = [ { "type": "simplex", @@ -479,7 +480,6 @@ class Test(TestSimplex2): for sc in swap_clients: sc._use_direct_message_routes = False - sc._smsg_plaintext_version = 2 assert len(swap_clients[0].active_networks) == 1 assert swap_clients[0].active_networks[0]["type"] == "simplex" @@ -538,7 +538,6 @@ class Test(TestSimplex2): for sc in swap_clients: sc._use_direct_message_routes = False - sc._smsg_plaintext_version = 2 assert len(swap_clients[0].active_networks) == 1 assert swap_clients[0].active_networks[0]["type"] == "simplex" @@ -597,7 +596,6 @@ class Test(TestSimplex2): for sc in swap_clients: sc._use_direct_message_routes = True - sc._smsg_plaintext_version = 2 assert len(swap_clients[0].active_networks) == 1 assert swap_clients[0].active_networks[0]["type"] == "simplex" @@ -672,7 +670,6 @@ class Test(TestSimplex2): for sc in swap_clients: sc._use_direct_message_routes = True - sc._smsg_plaintext_version = 2 assert len(swap_clients[0].active_networks) == 1 assert swap_clients[0].active_networks[0]["type"] == "simplex" @@ -745,7 +742,6 @@ class Test(TestSimplex2): for sc in swap_clients: sc._use_direct_message_routes = False - sc._smsg_plaintext_version = 2 assert len(swap_clients[0].active_networks) == 1 assert swap_clients[0].active_networks[0]["type"] == "simplex"