network: Automatically set direct simplex mode per bid.

This commit is contained in:
tecnovert
2025-08-03 20:23:55 +02:00
parent 6b218773dc
commit 1ea8b80bdc
5 changed files with 117 additions and 61 deletions

View File

@@ -2296,7 +2296,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
offer_bytes = msg_buf.to_bytes() offer_bytes = msg_buf.to_bytes()
payload_hex = str.format("{:02x}", MessageTypes.OFFER) + offer_bytes.hex() payload_hex = str.format("{:02x}", MessageTypes.OFFER) + offer_bytes.hex()
msg_valid: int = max(self.SMSG_SECONDS_IN_HOUR, valid_for_seconds) 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_id = self.sendMessage(
offer_addr, offer_addr_to, payload_hex, msg_valid, cursor offer_addr, offer_addr_to, payload_hex, msg_valid, cursor
) )
@@ -3277,8 +3277,11 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
"amount_from": amount, "amount_from": amount,
"amount_to": amount_to, "amount_to": amount_to,
} }
bid_message_nets = self.selectMessageNetStringForConcept(
Concepts.OFFER, offer_id, offer.message_nets, cursor
)
route_id, route_established = self.prepareMessageRoute( route_id, route_established = self.prepareMessageRoute(
MessageNetworks.SIMPLEX, bid_message_nets,
request_data, request_data,
bid_addr, bid_addr,
offer.addr_from, offer.addr_from,
@@ -3291,10 +3294,6 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
dt.datetime.fromtimestamp(now).date(), contract_count dt.datetime.fromtimestamp(now).date(), contract_count
) )
bid_message_nets = self.selectMessageNetStringForConcept(
Concepts.OFFER, offer_id, offer.message_nets, cursor
)
bid = Bid( bid = Bid(
protocol_version=PROTOCOL_VERSION_SECRET_HASH, protocol_version=PROTOCOL_VERSION_SECRET_HASH,
active_ind=1, active_ind=1,
@@ -3905,7 +3904,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
def prepareMessageRoute( def prepareMessageRoute(
self, self,
network_id, message_nets,
req_data, req_data,
addr_from: str, addr_from: str,
addr_to: str, addr_to: str,
@@ -3915,9 +3914,34 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
if self._use_direct_message_routes is False: if self._use_direct_message_routes is False:
return None, 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: try:
net_i = self.getActiveNetworkInterface(2) net_i = self.getActiveNetworkInterface(network_id)
except Exception as e: # noqa: F841 except Exception as e: # noqa: F841
self.logD(
LC.NET,
f"Not using route - network interface not found for {network_id}.",
)
return None, False return None, False
# Look for active route # 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) msg_valid: int = max(self.SMSG_SECONDS_IN_HOUR, valid_for_seconds)
connect_req_msgid = self.sendMessage( 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() now: int = self.getTime()
message_route = DirectMessageRoute( message_route = DirectMessageRoute(
active_ind=2, active_ind=2,
network_id=2, network_id=network_id,
linked_type=Concepts.OFFER, linked_type=Concepts.OFFER,
smsg_addr_local=addr_from, smsg_addr_local=addr_from,
smsg_addr_remote=addr_to, smsg_addr_remote=addr_to,
@@ -4034,8 +4063,11 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
"amount_from": amount, "amount_from": amount,
"amount_to": amount_to, "amount_to": amount_to,
} }
bid_message_nets = self.selectMessageNetStringForConcept(
Concepts.OFFER, offer.offer_id, offer.message_nets, cursor
)
route_id, route_established = self.prepareMessageRoute( route_id, route_established = self.prepareMessageRoute(
MessageNetworks.SIMPLEX, bid_message_nets,
request_data, request_data,
bid_addr, bid_addr,
offer.addr_from, offer.addr_from,
@@ -4043,9 +4075,6 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
valid_for_seconds, 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) reverse_bid: bool = self.is_reverse_ads_bid(coin_from, coin_to)
if reverse_bid: if reverse_bid:
reversed_rate: int = ci_to.make_int(amount / amount_to, r=1) 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.") self.log.debug("Ignoring expired offer.")
return return
_ = self.expandMessageNets(offer_data.message_nets) # Decode to validate self.validateMessageNets(offer_data.message_nets)
offer_rate: int = ci_from.make_int( offer_rate: int = ci_from.make_int(
offer_data.amount_to / offer_data.amount_from, r=1 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) 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.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: str = msg.get("msg_net", "smsg")
network_type_received_on_id: int = networkTypeToID(network_type) 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: if ci_to.curve_type() == Curves.ed25519:
ensure(len(bid_data.kbsf_dleag) <= 16000, "Invalid kbsf_dleag size") 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"]) 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(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)}.") 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_from = self.ci(offer.coin_to)
ci_to = self.ci(offer.coin_from) 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.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"]) bid_id = bytes.fromhex(msg["msgid"])
@@ -10197,7 +10226,7 @@ class BasicSwap(BaseApp, BSXNetwork, UIApp):
offer_id = bytes.fromhex(req_data["offer_id"]) offer_id = bytes.fromhex(req_data["offer_id"])
bidder_addr = req_data["bsx_address"] bidder_addr = req_data["bsx_address"]
net_i = self.getActiveNetworkInterface(2) net_i = self.getActiveNetworkInterface(MessageNetworks.SIMPLEX)
try: try:
cursor = self.openDB() cursor = self.openDB()
offer = self.getOffer(offer_id, cursor) offer = self.getOffer(offer_id, cursor)

View File

@@ -724,7 +724,7 @@ class DirectMessageRouteLink(Table):
class NetworkPortal(Table): class NetworkPortal(Table):
__tablename__ = "network_portals" __tablename__ = "network_portals"
def __init__( def set(
self, time_start, time_valid, network_from, network_to, address_from, address_to self, time_start, time_valid, network_from, network_to, address_from, address_to
): ):
super().__init__() super().__init__()

View File

@@ -50,15 +50,18 @@ def networkTypeToID(type: str) -> int:
return MessageNetworks.SMSG return MessageNetworks.SMSG
elif type == "simplex": elif type == "simplex":
return MessageNetworks.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: if id == MessageNetworks.SMSG:
return "smsg" network_name = "smsg"
elif id == MessageNetworks.SIMPLEX: elif id == MessageNetworks.SIMPLEX:
return "simplex" network_name = "simplex"
raise RuntimeError(f"Unknown message network id: {id}") else:
raise RuntimeError(f"Unknown message network id: {id}")
return ("b." if bridged else "") + network_name
class BSXNetwork: class BSXNetwork:
@@ -350,7 +353,11 @@ class BSXNetwork:
for bridged_network in network.get("bridged", []): for bridged_network in network.get("bridged", []):
bridged_network_type = bridged_network.get("type", "smsg") bridged_network_type = bridged_network.get("type", "smsg")
bridged_networks_set.add(bridged_network_type) 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) return ",".join(all_networks)
def selectMessageNetString( def selectMessageNetString(
@@ -366,26 +373,42 @@ class BSXNetwork:
for bridged_network in network.get("bridged", []): for bridged_network in network.get("bridged", []):
bridged_network_type = bridged_network.get("type", "smsg") bridged_network_type = bridged_network.get("type", "smsg")
bridged_networks_set.add(networkTypeToID(bridged_network_type)) 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))) return networkIDToType(random.choice(tuple(active_networks_set)))
# Choose which network to respond on # 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 # else prefer a network the local node has active
for received_on_id in received_on_network_ids: for received_on_id in received_on_network_ids:
if ( if (
received_on_id in active_networks_set 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) return networkIDToType(received_on_id)
# Prefer to use a network both nodes have active
for local_net_id in active_networks_set: 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) 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: for local_net_id in bridged_networks_set:
if local_net_id in remote_network_ids: if local_net_id in remote_active_network_ids:
return networkIDToType(local_net_id) 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") raise RuntimeError("Unable to select network to respond on")
def selectMessageNetStringForConcept( def selectMessageNetStringForConcept(
@@ -409,18 +432,30 @@ class BSXNetwork:
return self.selectMessageNetString(received_on_network_ids, remote_message_nets) 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: if message_nets is None or len(message_nets) < 1:
return [] return [], []
if len(message_nets) > 256: if len(message_nets) > 256:
raise ValueError("message_nets string is too large") raise ValueError("message_nets string is too large")
rv = [] active_networks = []
bridged_networks = []
for network_string in message_nets.split(","): 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: try:
rv.append(networkTypeToID(network_string)) network_id: int = networkTypeToID(network_string)
except Exception as e: # noqa: F841 except Exception as e: # noqa: F841
self.log.debug(f"Unknown message_net {network_string}") 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( def getMessageRoute(
self, network_id: int, address_from: str, address_to: str, cursor=None self, network_id: int, address_from: str, address_to: str, cursor=None
@@ -463,12 +498,14 @@ class BSXNetwork:
linked_id=None, linked_id=None,
timestamp=None, timestamp=None,
deterministic=False, deterministic=False,
message_nets=None, # None -> all, else message_nets=None, # None|empty -> all
) -> bytes: ) -> bytes:
message_id: bytes = None message_id: bytes = None
networks_list = self.expandMessageNets( active_networks_list, bridged_networks_list = self.expandMessageNets(
message_nets 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() networks_sent_to = set()
# Message routes work only with simplex messages for now. # 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) message_route = self.getMessageRoute(2, addr_from, addr_to, cursor=cursor)
if message_route: if message_route:
network = self.getActiveNetwork(2) network = self.getActiveNetwork(MessageNetworks.SIMPLEX)
net_i = network["ws_thread"] net_i = network["ws_thread"]
remote_name = None remote_name = None
@@ -699,7 +736,7 @@ class BSXNetwork:
self.num_smsg_messages_sent += 1 self.num_smsg_messages_sent += 1
def processContactDisconnected(self, event_data) -> None: def processContactDisconnected(self, event_data) -> None:
net_i = self.getActiveNetworkInterface(2) net_i = self.getActiveNetworkInterface(MessageNetworks.SIMPLEX)
connId = getResponseData(event_data, "contact")["activeConn"]["connId"] connId = getResponseData(event_data, "contact")["activeConn"]["connId"]
self.log.info(f"Direct message route disconnected, connId: {connId}") self.log.info(f"Direct message route disconnected, connId: {connId}")
closeSimplexChat(self, net_i, connId) closeSimplexChat(self, net_i, connId)
@@ -727,7 +764,7 @@ class BSXNetwork:
self.closeDB(cursor) self.closeDB(cursor)
def closeMessageRoute(self, record_id, network_id, route_data, 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"] connId = route_data["pccConnId"]
@@ -774,7 +811,8 @@ class BSXNetwork:
addr_portal: str = self.prepareSMSGAddress( addr_portal: str = self.prepareSMSGAddress(
None, AddressTypes.PORTAL_LOCAL, cursor None, AddressTypes.PORTAL_LOCAL, cursor
) )
portal = NetworkPortal( portal = NetworkPortal()
portal.set(
now, 30 * 60, network_from_id, network_to_id, addr_portal, addr_to now, 30 * 60, network_from_id, network_to_id, addr_portal, addr_to
) )
portal.created_at = now portal.created_at = now
@@ -971,7 +1009,8 @@ class BSXNetwork:
}, },
) )
if received_portal is None: if received_portal is None:
received_portal = NetworkPortal( received_portal = NetworkPortal()
received_portal.set(
time_start, time_start,
portal_data.time_valid, portal_data.time_valid,
portal_data.network_type_from, portal_data.network_type_from,

View File

@@ -106,8 +106,6 @@ class Test(TestSimplex2):
logger.info("---------- Test multinet swap across networks") logger.info("---------- Test multinet swap across networks")
swap_clients = self.swap_clients swap_clients = self.swap_clients
for sc in swap_clients:
sc._use_direct_message_routes = False
swap_clients[2]._bridge_networks = True swap_clients[2]._bridge_networks = True
assert len(swap_clients[0].active_networks) == 1 assert len(swap_clients[0].active_networks) == 1
@@ -163,8 +161,6 @@ class Test(TestSimplex2):
logger.info("---------- Test reversed swap across networks") logger.info("---------- Test reversed swap across networks")
swap_clients = self.swap_clients swap_clients = self.swap_clients
for sc in swap_clients:
sc._use_direct_message_routes = False
swap_clients[2]._bridge_networks = True swap_clients[2]._bridge_networks = True
coin_from = Coins.XMR coin_from = Coins.XMR
@@ -214,9 +210,8 @@ class Test(TestSimplex2):
logger.info("---------- Test secret hash swap across networks") logger.info("---------- Test secret hash swap across networks")
swap_clients = self.swap_clients swap_clients = self.swap_clients
for sc in swap_clients:
sc._use_direct_message_routes = False
swap_clients[2]._bridge_networks = True swap_clients[2]._bridge_networks = True
coin_from = Coins.PART coin_from = Coins.PART
coin_to = Coins.BTC coin_to = Coins.BTC
self.prepare_balance(coin_to, 100.0, 1801, 1800) 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 # Messages for bids should only be sent to one network
swap_clients = self.swap_clients swap_clients = self.swap_clients
for sc in swap_clients:
sc._use_direct_message_routes = False
swap_clients[2]._bridge_networks = True swap_clients[2]._bridge_networks = True
assert len(swap_clients[2].active_networks) == 2 assert len(swap_clients[2].active_networks) == 2

View File

@@ -457,6 +457,7 @@ class TestSimplex2(BaseTest):
@classmethod @classmethod
def addCoinSettings(cls, settings, datadir, node_id): def addCoinSettings(cls, settings, datadir, node_id):
settings["smsg_plaintext_version"] = 2
settings["networks"] = [ settings["networks"] = [
{ {
"type": "simplex", "type": "simplex",
@@ -479,7 +480,6 @@ class Test(TestSimplex2):
for sc in swap_clients: for sc in swap_clients:
sc._use_direct_message_routes = False sc._use_direct_message_routes = False
sc._smsg_plaintext_version = 2
assert len(swap_clients[0].active_networks) == 1 assert len(swap_clients[0].active_networks) == 1
assert swap_clients[0].active_networks[0]["type"] == "simplex" assert swap_clients[0].active_networks[0]["type"] == "simplex"
@@ -538,7 +538,6 @@ class Test(TestSimplex2):
for sc in swap_clients: for sc in swap_clients:
sc._use_direct_message_routes = False sc._use_direct_message_routes = False
sc._smsg_plaintext_version = 2
assert len(swap_clients[0].active_networks) == 1 assert len(swap_clients[0].active_networks) == 1
assert swap_clients[0].active_networks[0]["type"] == "simplex" assert swap_clients[0].active_networks[0]["type"] == "simplex"
@@ -597,7 +596,6 @@ class Test(TestSimplex2):
for sc in swap_clients: for sc in swap_clients:
sc._use_direct_message_routes = True sc._use_direct_message_routes = True
sc._smsg_plaintext_version = 2
assert len(swap_clients[0].active_networks) == 1 assert len(swap_clients[0].active_networks) == 1
assert swap_clients[0].active_networks[0]["type"] == "simplex" assert swap_clients[0].active_networks[0]["type"] == "simplex"
@@ -672,7 +670,6 @@ class Test(TestSimplex2):
for sc in swap_clients: for sc in swap_clients:
sc._use_direct_message_routes = True sc._use_direct_message_routes = True
sc._smsg_plaintext_version = 2
assert len(swap_clients[0].active_networks) == 1 assert len(swap_clients[0].active_networks) == 1
assert swap_clients[0].active_networks[0]["type"] == "simplex" assert swap_clients[0].active_networks[0]["type"] == "simplex"
@@ -745,7 +742,6 @@ class Test(TestSimplex2):
for sc in swap_clients: for sc in swap_clients:
sc._use_direct_message_routes = False sc._use_direct_message_routes = False
sc._smsg_plaintext_version = 2
assert len(swap_clients[0].active_networks) == 1 assert len(swap_clients[0].active_networks) == 1
assert swap_clients[0].active_networks[0]["type"] == "simplex" assert swap_clients[0].active_networks[0]["type"] == "simplex"