mirror of
https://github.com/basicswap/basicswap.git
synced 2025-11-05 10:28:10 +01:00
network: Use Simplex direct chats.
This commit is contained in:
@@ -184,7 +184,7 @@ def wait_for_bid(
|
||||
swap_client.log.debug(
|
||||
f"TEST: wait_for_bid {bid_id.hex()}: Bid not found."
|
||||
)
|
||||
raise ValueError("wait_for_bid timed out.")
|
||||
raise ValueError(f"wait_for_bid timed out {bid_id.hex()}.")
|
||||
|
||||
|
||||
def wait_for_bid_tx_state(
|
||||
@@ -331,7 +331,7 @@ def wait_for_balance(
|
||||
delay_event.wait(delay_time)
|
||||
i += 1
|
||||
if i > iterations:
|
||||
raise ValueError("Expect {} {}".format(balance_key, expect_amount))
|
||||
raise ValueError(f"Expect {balance_key} {expect_amount}")
|
||||
|
||||
|
||||
def wait_for_unspent(
|
||||
@@ -347,11 +347,11 @@ def wait_for_unspent(
|
||||
delay_event.wait(delay_time)
|
||||
i += 1
|
||||
if i > iterations:
|
||||
raise ValueError("wait_for_unspent {}".format(expect_amount))
|
||||
raise ValueError(f"wait_for_unspent {expect_amount}")
|
||||
|
||||
|
||||
def delay_for(delay_event, delay_for=60):
|
||||
logging.info("Delaying for {} seconds.".format(delay_for))
|
||||
logging.info(f"Delaying for {delay_for} seconds.")
|
||||
delay_event.wait(delay_for)
|
||||
|
||||
|
||||
@@ -375,9 +375,7 @@ def waitForRPC(rpc_func, delay_event, rpc_command="getwalletinfo", max_tries=7):
|
||||
except Exception as ex:
|
||||
if i < max_tries:
|
||||
logging.warning(
|
||||
"Can't connect to RPC: %s. Retrying in %d second/s.",
|
||||
str(ex),
|
||||
(i + 1),
|
||||
f"Can't connect to RPC: {ex}. Retrying in {i + 1} second/s."
|
||||
)
|
||||
delay_event.wait(i + 1)
|
||||
raise ValueError("waitForRPC failed")
|
||||
|
||||
@@ -89,15 +89,19 @@ DOGECOIN_RPC_PORT_BASE = int(os.getenv("DOGECOIN_RPC_PORT_BASE", DOGE_BASE_RPC_P
|
||||
EXTRA_CONFIG_JSON = json.loads(os.getenv("EXTRA_CONFIG_JSON", "{}"))
|
||||
|
||||
|
||||
def waitForBidState(delay_event, port, bid_id, state_str, wait_for=60):
|
||||
def waitForBidState(delay_event, port, bid_id, wait_for_state, wait_for=60):
|
||||
for i in range(wait_for):
|
||||
if delay_event.is_set():
|
||||
raise ValueError("Test stopped.")
|
||||
bid = json.loads(
|
||||
urlopen("http://127.0.0.1:12700/json/bids/{}".format(bid_id)).read()
|
||||
)
|
||||
if bid["bid_state"] == state_str:
|
||||
return
|
||||
if isinstance(wait_for_state, (list, tuple)):
|
||||
if bid["bid_state"] in wait_for_state:
|
||||
return
|
||||
else:
|
||||
if bid["bid_state"] == wait_for_state:
|
||||
return
|
||||
delay_event.wait(1)
|
||||
raise ValueError("waitForBidState failed")
|
||||
|
||||
|
||||
@@ -17,8 +17,7 @@ docker run \
|
||||
|
||||
Fingerprint: Q8SNxc2SRcKyXlhJM8KFUgPNW4KXPGRm4eSLtT_oh-I=
|
||||
|
||||
export SIMPLEX_SERVER_ADDRESS=smp://Q8SNxc2SRcKyXlhJM8KFUgPNW4KXPGRm4eSLtT_oh-I=:password@127.0.0.1:5223,443
|
||||
|
||||
export SIMPLEX_SERVER_ADDRESS=smp://Q8SNxc2SRcKyXlhJM8KFUgPNW4KXPGRm4eSLtT_oh-I=:password@127.0.0.1:5223
|
||||
|
||||
https://github.com/simplex-chat/simplex-chat/issues/4127
|
||||
json: {"corrId":"3","cmd":"/_send #1 text test123"}
|
||||
@@ -53,10 +52,14 @@ from tests.basicswap.common import (
|
||||
wait_for_bid,
|
||||
wait_for_offer,
|
||||
)
|
||||
from tests.basicswap.util import read_json_api
|
||||
from tests.basicswap.test_xmr import BaseTest, test_delay_event, RESET_TEST
|
||||
|
||||
|
||||
SIMPLEX_SERVER_ADDRESS = os.getenv("SIMPLEX_SERVER_ADDRESS")
|
||||
SIMPLEX_SERVER_FINGERPRINT = os.getenv("SIMPLEX_SERVER_FINGERPRINT", "")
|
||||
SIMPLEX_SERVER_ADDRESS = os.getenv(
|
||||
"SIMPLEX_SERVER_ADDRESS",
|
||||
f"smp://{SIMPLEX_SERVER_FINGERPRINT}:password@127.0.0.1:5223",
|
||||
)
|
||||
SIMPLEX_CLIENT_PATH = os.path.expanduser(os.getenv("SIMPLEX_CLIENT_PATH"))
|
||||
TEST_DIR = cfg.TEST_DATADIRS
|
||||
|
||||
@@ -67,6 +70,32 @@ if not len(logger.handlers):
|
||||
logger.addHandler(logging.StreamHandler(sys.stdout))
|
||||
|
||||
|
||||
def parse_message(msg_data):
|
||||
if msg_data["resp"]["type"] not in ("chatItemsStatusesUpdated", "newChatItems"):
|
||||
return None
|
||||
|
||||
for chat_item in msg_data["resp"]["chatItems"]:
|
||||
chat_type: str = chat_item["chatInfo"]["type"]
|
||||
if chat_type == "group":
|
||||
chat_name = chat_item["chatInfo"]["groupInfo"]["localDisplayName"]
|
||||
elif chat_type == "direct":
|
||||
chat_name = chat_item["chatInfo"]["contact"]["localDisplayName"]
|
||||
else:
|
||||
return None
|
||||
|
||||
dir_type = chat_item["chatItem"]["meta"]["itemStatus"]["type"]
|
||||
msg_dir = "recv" if dir_type == "rcvNew" else "sent"
|
||||
if dir_type in ("sndRcvd", "rcvNew"):
|
||||
msg_content = chat_item["chatItem"]["content"]["msgContent"]["text"]
|
||||
return {
|
||||
"text": msg_content,
|
||||
"chat_type": chat_type,
|
||||
"chat_name": chat_name,
|
||||
"msg_dir": msg_dir,
|
||||
}
|
||||
return None
|
||||
|
||||
|
||||
class TestSimplex(unittest.TestCase):
|
||||
daemons = []
|
||||
remove_testdir: bool = False
|
||||
@@ -79,10 +108,10 @@ class TestSimplex(unittest.TestCase):
|
||||
|
||||
if os.path.isdir(TEST_DIR):
|
||||
if RESET_TEST:
|
||||
logging.info("Removing " + TEST_DIR)
|
||||
logger.info("Removing " + TEST_DIR)
|
||||
shutil.rmtree(TEST_DIR)
|
||||
else:
|
||||
logging.info("Restoring instance from " + TEST_DIR)
|
||||
logger.info("Restoring instance from " + TEST_DIR)
|
||||
if not os.path.exists(TEST_DIR):
|
||||
os.makedirs(TEST_DIR)
|
||||
|
||||
@@ -134,8 +163,8 @@ class TestSimplex(unittest.TestCase):
|
||||
ws_thread.send_command("/set reactions #bsx off")
|
||||
ws_thread.send_command("/set reports #bsx off")
|
||||
ws_thread.send_command("/set disappear #bsx on week")
|
||||
sent_id = ws_thread.send_command("/create link #bsx")
|
||||
|
||||
sent_id = ws_thread.send_command("/create link #bsx")
|
||||
connReqContact = None
|
||||
connReqMsgData = waitForResponse(ws_thread, sent_id, test_delay_event)
|
||||
connReqContact = connReqMsgData["resp"]["connReqContact"]
|
||||
@@ -151,65 +180,199 @@ class TestSimplex(unittest.TestCase):
|
||||
response = waitForResponse(ws_thread2, sent_id, test_delay_event)
|
||||
assert len(response["resp"]["groups"]) == 1
|
||||
|
||||
ws_thread.send_command("#bsx test msg 1")
|
||||
sent_id = ws_thread2.send_command("/connect")
|
||||
response = waitForResponse(ws_thread2, sent_id, test_delay_event)
|
||||
with open(os.path.join(client2_dir, "chat_inv.txt"), "w") as fp:
|
||||
fp.write(json.dumps(response, indent=4))
|
||||
|
||||
connReqInvitation = response["resp"]["connReqInvitation"]
|
||||
logger.info(f"direct_link: {connReqInvitation}")
|
||||
pccConnId_2_sent = response["resp"]["connection"]["pccConnId"]
|
||||
print(f"pccConnId_2_sent: {pccConnId_2_sent}")
|
||||
|
||||
sent_id = ws_thread.send_command(f"/connect {connReqInvitation}")
|
||||
response = waitForResponse(ws_thread, sent_id, test_delay_event)
|
||||
with open(os.path.join(client1_dir, "chat_inv_accept.txt"), "w") as fp:
|
||||
fp.write(json.dumps(response, indent=4))
|
||||
pccConnId_1_accepted = response["resp"]["connection"]["pccConnId"]
|
||||
print(f"pccConnId_1_accepted: {pccConnId_1_accepted}")
|
||||
|
||||
sent_id = ws_thread.send_command("/chats")
|
||||
response = waitForResponse(ws_thread, sent_id, test_delay_event)
|
||||
with open(os.path.join(client1_dir, "chats.txt"), "w") as fp:
|
||||
fp.write(json.dumps(response, indent=4))
|
||||
|
||||
direct_local_name_1 = None
|
||||
for chat in response["resp"]["chats"]:
|
||||
print(f"chat: {chat}")
|
||||
if (
|
||||
chat["chatInfo"]["contact"]["activeConn"]["connId"]
|
||||
== pccConnId_1_accepted
|
||||
):
|
||||
direct_local_name_1 = chat["chatInfo"]["contact"][
|
||||
"localDisplayName"
|
||||
]
|
||||
break
|
||||
print(f"direct_local_name_1: {direct_local_name_1}")
|
||||
|
||||
sent_id = ws_thread2.send_command("/chats")
|
||||
response = waitForResponse(ws_thread2, sent_id, test_delay_event)
|
||||
with open(os.path.join(client2_dir, "chats.txt"), "w") as fp:
|
||||
fp.write(json.dumps(response, indent=4))
|
||||
|
||||
direct_local_name_2 = None
|
||||
for chat in response["resp"]["chats"]:
|
||||
print(f"chat: {chat}")
|
||||
if (
|
||||
chat["chatInfo"]["contact"]["activeConn"]["connId"]
|
||||
== pccConnId_2_sent
|
||||
):
|
||||
direct_local_name_2 = chat["chatInfo"]["contact"][
|
||||
"localDisplayName"
|
||||
]
|
||||
break
|
||||
print(f"direct_local_name_2: {direct_local_name_2}")
|
||||
# localDisplayName in chats doesn't match the contactConnected message.
|
||||
assert direct_local_name_1 == "user_1"
|
||||
assert direct_local_name_2 == "user_1"
|
||||
|
||||
sent_id = ws_thread.send_command("#bsx test msg 1")
|
||||
response = waitForResponse(ws_thread, sent_id, test_delay_event)
|
||||
assert response["resp"]["type"] == "newChatItems"
|
||||
sent_id = ws_thread.send_command("@user_1 test msg 2")
|
||||
response = waitForResponse(ws_thread, sent_id, test_delay_event)
|
||||
assert response["resp"]["type"] == "newChatItems"
|
||||
|
||||
msg_counter1: int = 0
|
||||
msg_counter2: int = 0
|
||||
found = [dict(), dict()]
|
||||
found_connected = [dict(), dict()]
|
||||
|
||||
found_1 = False
|
||||
found_2 = False
|
||||
for i in range(100):
|
||||
message = ws_thread.queue_get()
|
||||
if message is not None:
|
||||
if test_delay_event.is_set():
|
||||
break
|
||||
for k in range(100):
|
||||
message = ws_thread.queue_get()
|
||||
if message is None or test_delay_event.is_set():
|
||||
break
|
||||
msg_counter1 += 1
|
||||
data = json.loads(message)
|
||||
# print(f"message 1: {json.dumps(data, indent=4)}")
|
||||
try:
|
||||
if data["resp"]["type"] in (
|
||||
"chatItemsStatusesUpdated",
|
||||
"newChatItems",
|
||||
):
|
||||
for chat_item in data["resp"]["chatItems"]:
|
||||
# print(f"chat_item 1: {json.dumps(chat_item, indent=4)}")
|
||||
if chat_item["chatItem"]["meta"]["itemStatus"][
|
||||
"type"
|
||||
] in ("sndRcvd", "rcvNew"):
|
||||
if (
|
||||
chat_item["chatItem"]["content"]["msgContent"][
|
||||
"text"
|
||||
]
|
||||
== "test msg 1"
|
||||
):
|
||||
found_1 = True
|
||||
msg_type = data["resp"]["type"]
|
||||
except Exception as e:
|
||||
print(f"msg_type error: {e}")
|
||||
msg_type = "None"
|
||||
with open(
|
||||
os.path.join(
|
||||
client1_dir, f"recv_{msg_counter1}_{msg_type}.txt"
|
||||
),
|
||||
"w",
|
||||
) as fp:
|
||||
fp.write(json.dumps(data, indent=4))
|
||||
if msg_type == "contactConnected":
|
||||
found_connected[0][msg_counter1] = data
|
||||
continue
|
||||
try:
|
||||
simplex_msg = parse_message(data)
|
||||
if simplex_msg:
|
||||
simplex_msg["msg_id"] = msg_counter1
|
||||
found[0][msg_counter1] = simplex_msg
|
||||
except Exception as e:
|
||||
print(f"error 1: {e}")
|
||||
|
||||
message = ws_thread2.queue_get()
|
||||
if message is not None:
|
||||
for k in range(100):
|
||||
message = ws_thread2.queue_get()
|
||||
if message is None or test_delay_event.is_set():
|
||||
break
|
||||
msg_counter2 += 1
|
||||
data = json.loads(message)
|
||||
# print(f"message 2: {json.dumps(data, indent=4)}")
|
||||
try:
|
||||
if data["resp"]["type"] in (
|
||||
"chatItemsStatusesUpdated",
|
||||
"newChatItems",
|
||||
):
|
||||
for chat_item in data["resp"]["chatItems"]:
|
||||
# print(f"chat_item 1: {json.dumps(chat_item, indent=4)}")
|
||||
if chat_item["chatItem"]["meta"]["itemStatus"][
|
||||
"type"
|
||||
] in ("sndRcvd", "rcvNew"):
|
||||
if (
|
||||
chat_item["chatItem"]["content"]["msgContent"][
|
||||
"text"
|
||||
]
|
||||
== "test msg 1"
|
||||
):
|
||||
found_2 = True
|
||||
msg_type = data["resp"]["type"]
|
||||
except Exception as e:
|
||||
print(f"msg_type error: {e}")
|
||||
msg_type = "None"
|
||||
with open(
|
||||
os.path.join(
|
||||
client2_dir, f"recv_{msg_counter2}_{msg_type}.txt"
|
||||
),
|
||||
"w",
|
||||
) as fp:
|
||||
fp.write(json.dumps(data, indent=4))
|
||||
if msg_type == "contactConnected":
|
||||
found_connected[1][msg_counter2] = data
|
||||
continue
|
||||
try:
|
||||
simplex_msg = parse_message(data)
|
||||
if simplex_msg:
|
||||
simplex_msg["msg_id"] = msg_counter2
|
||||
found[1][msg_counter2] = simplex_msg
|
||||
except Exception as e:
|
||||
print(f"error 2: {e}")
|
||||
|
||||
if found_1 and found_2:
|
||||
if (
|
||||
len(found[0]) >= 2
|
||||
and len(found[1]) >= 2
|
||||
and len(found_connected[0]) >= 1
|
||||
and len(found_connected[1]) >= 1
|
||||
):
|
||||
break
|
||||
test_delay_event.wait(0.5)
|
||||
|
||||
assert found_1 is True
|
||||
assert found_2 is True
|
||||
assert len(found_connected[0]) == 1
|
||||
node1_connect = list(found_connected[0].values())[0]
|
||||
assert (
|
||||
node1_connect["resp"]["contact"]["activeConn"]["connId"]
|
||||
== pccConnId_1_accepted
|
||||
)
|
||||
assert node1_connect["resp"]["contact"]["localDisplayName"] == "user_2"
|
||||
|
||||
assert len(found_connected[1]) == 1
|
||||
node2_connect = list(found_connected[1].values())[0]
|
||||
assert (
|
||||
node2_connect["resp"]["contact"]["activeConn"]["connId"]
|
||||
== pccConnId_2_sent
|
||||
)
|
||||
assert node2_connect["resp"]["contact"]["localDisplayName"] == "user_2"
|
||||
|
||||
node1_msg1 = [m for m in found[0].values() if m["text"] == "test msg 1"]
|
||||
assert len(node1_msg1) == 1
|
||||
node1_msg1 = node1_msg1[0]
|
||||
assert node1_msg1["chat_type"] == "group"
|
||||
assert node1_msg1["chat_name"] == "bsx"
|
||||
assert node1_msg1["msg_dir"] == "sent"
|
||||
node1_msg2 = [m for m in found[0].values() if m["text"] == "test msg 2"]
|
||||
assert len(node1_msg2) == 1
|
||||
node1_msg2 = node1_msg2[0]
|
||||
assert node1_msg2["chat_type"] == "direct"
|
||||
assert node1_msg2["chat_name"] == "user_1"
|
||||
assert node1_msg2["msg_dir"] == "sent"
|
||||
|
||||
node2_msg1 = [m for m in found[1].values() if m["text"] == "test msg 1"]
|
||||
assert len(node2_msg1) == 1
|
||||
node2_msg1 = node2_msg1[0]
|
||||
assert node2_msg1["chat_type"] == "group"
|
||||
assert node2_msg1["chat_name"] == "bsx"
|
||||
assert node2_msg1["msg_dir"] == "recv"
|
||||
node2_msg2 = [m for m in found[1].values() if m["text"] == "test msg 2"]
|
||||
assert len(node2_msg2) == 1
|
||||
node2_msg2 = node2_msg2[0]
|
||||
assert node2_msg2["chat_type"] == "direct"
|
||||
assert node2_msg2["chat_name"] == "user_1"
|
||||
assert node2_msg2["msg_dir"] == "recv"
|
||||
|
||||
sent_id = ws_thread.send_command("/delete @user_1")
|
||||
response = waitForResponse(ws_thread, sent_id, test_delay_event)
|
||||
assert response["resp"]["type"] == "contactDeleted"
|
||||
|
||||
sent_id = ws_thread2.send_command("/delete @user_1")
|
||||
response = waitForResponse(ws_thread2, sent_id, test_delay_event)
|
||||
assert response["resp"]["type"] == "contactDeleted"
|
||||
|
||||
sent_id = ws_thread2.send_command("/chats")
|
||||
response = waitForResponse(ws_thread2, sent_id, test_delay_event)
|
||||
with open(os.path.join(client2_dir, "chats_after_delete.txt"), "w") as fp:
|
||||
fp.write(json.dumps(response, indent=4))
|
||||
|
||||
assert len(response["resp"]["chats"]) == 4
|
||||
|
||||
finally:
|
||||
for t in threads:
|
||||
@@ -277,7 +440,7 @@ class Test(BaseTest):
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(cls):
|
||||
logging.info("Finalising Test")
|
||||
logger.info("Finalising Test")
|
||||
super(Test, cls).tearDownClass()
|
||||
stopDaemons(cls.daemons)
|
||||
|
||||
@@ -295,17 +458,24 @@ class Test(BaseTest):
|
||||
]
|
||||
|
||||
def test_01_swap(self):
|
||||
logging.info("---------- Test xmr swap")
|
||||
logger.info("---------- Test adaptor sig swap")
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
for sc in swap_clients:
|
||||
sc.dleag_split_size_init = 9000
|
||||
sc.dleag_split_size = 11000
|
||||
sc._dleag_split_size_init = 9000
|
||||
sc._dleag_split_size = 11000
|
||||
sc._use_direct_messages = False
|
||||
|
||||
assert len(swap_clients[0].active_networks) == 1
|
||||
assert swap_clients[0].active_networks[0]["type"] == "simplex"
|
||||
|
||||
num_direct_messages_received_before = [0] * 3
|
||||
for i in range(3):
|
||||
num_direct_messages_received_before[i] = swap_clients[
|
||||
i
|
||||
].num_direct_simplex_messages_received
|
||||
|
||||
coin_from = Coins.BTC
|
||||
coin_to = self.coin_to
|
||||
|
||||
@@ -340,3 +510,518 @@ class Test(BaseTest):
|
||||
sent=True,
|
||||
wait_for=320,
|
||||
)
|
||||
|
||||
for i in range(3):
|
||||
assert (
|
||||
num_direct_messages_received_before[i]
|
||||
== swap_clients[i].num_direct_simplex_messages_received
|
||||
)
|
||||
|
||||
def test_01_swap_reverse(self):
|
||||
logger.info("---------- Test adaptor sig swap reverse")
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
for sc in swap_clients:
|
||||
sc._dleag_split_size_init = 9000
|
||||
sc._dleag_split_size = 11000
|
||||
sc._use_direct_messages = False
|
||||
|
||||
assert len(swap_clients[0].active_networks) == 1
|
||||
assert swap_clients[0].active_networks[0]["type"] == "simplex"
|
||||
|
||||
num_direct_messages_received_before = [0] * 3
|
||||
for i in range(3):
|
||||
num_direct_messages_received_before[i] = swap_clients[
|
||||
i
|
||||
].num_direct_simplex_messages_received
|
||||
|
||||
coin_from = self.coin_to
|
||||
coin_to = Coins.BTC
|
||||
|
||||
ci_from = swap_clients[1].ci(coin_from)
|
||||
ci_to = swap_clients[0].ci(coin_to)
|
||||
|
||||
swap_value = ci_from.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
offer_id = swap_clients[1].postOffer(
|
||||
coin_from, coin_to, swap_value, rate_swap, swap_value, SwapTypes.XMR_SWAP
|
||||
)
|
||||
|
||||
wait_for_offer(test_delay_event, swap_clients[0], offer_id)
|
||||
offer = swap_clients[0].getOffer(offer_id)
|
||||
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(test_delay_event, swap_clients[1], bid_id, BidStates.BID_RECEIVED)
|
||||
swap_clients[1].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid(
|
||||
test_delay_event,
|
||||
swap_clients[1],
|
||||
bid_id,
|
||||
BidStates.SWAP_COMPLETED,
|
||||
wait_for=320,
|
||||
)
|
||||
wait_for_bid(
|
||||
test_delay_event,
|
||||
swap_clients[0],
|
||||
bid_id,
|
||||
BidStates.SWAP_COMPLETED,
|
||||
sent=True,
|
||||
wait_for=320,
|
||||
)
|
||||
|
||||
for i in range(3):
|
||||
assert (
|
||||
num_direct_messages_received_before[i]
|
||||
== swap_clients[i].num_direct_simplex_messages_received
|
||||
)
|
||||
|
||||
def test_02_direct(self):
|
||||
logger.info("---------- Test adaptor sig swap with direct messages")
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
for sc in swap_clients:
|
||||
sc._dleag_split_size_init = 9000
|
||||
sc._dleag_split_size = 11000
|
||||
sc._use_direct_message_routes = True
|
||||
|
||||
assert len(swap_clients[0].active_networks) == 1
|
||||
assert swap_clients[0].active_networks[0]["type"] == "simplex"
|
||||
|
||||
num_direct_messages_received_before = [0] * 3
|
||||
num_group_messages_received_before = [0] * 3
|
||||
for i in range(3):
|
||||
num_direct_messages_received_before[i] = swap_clients[
|
||||
i
|
||||
].num_direct_simplex_messages_received
|
||||
num_group_messages_received_before[i] = swap_clients[
|
||||
i
|
||||
].num_group_simplex_messages_received
|
||||
|
||||
coin_from = Coins.BTC
|
||||
coin_to = self.coin_to
|
||||
|
||||
ci_from = swap_clients[0].ci(coin_from)
|
||||
ci_to = swap_clients[1].ci(coin_to)
|
||||
|
||||
swap_value = ci_from.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
coin_from, coin_to, swap_value, rate_swap, swap_value, SwapTypes.XMR_SWAP
|
||||
)
|
||||
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(
|
||||
test_delay_event,
|
||||
swap_clients[0],
|
||||
bid_id,
|
||||
BidStates.BID_RECEIVED,
|
||||
wait_for=60,
|
||||
)
|
||||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid(
|
||||
test_delay_event,
|
||||
swap_clients[0],
|
||||
bid_id,
|
||||
BidStates.SWAP_COMPLETED,
|
||||
wait_for=320,
|
||||
)
|
||||
wait_for_bid(
|
||||
test_delay_event,
|
||||
swap_clients[1],
|
||||
bid_id,
|
||||
BidStates.SWAP_COMPLETED,
|
||||
sent=True,
|
||||
wait_for=320,
|
||||
)
|
||||
|
||||
for i in range(3):
|
||||
swap_clients[
|
||||
i
|
||||
].num_group_simplex_messages_received == num_group_messages_received_before[
|
||||
i
|
||||
] + 2
|
||||
swap_clients[
|
||||
2
|
||||
].num_direct_simplex_messages_received == num_direct_messages_received_before[2]
|
||||
|
||||
def test_02_direct_reverse(self):
|
||||
logger.info(
|
||||
"---------- Test test_02_direct_reverse adaptor sig swap with direct messages"
|
||||
)
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
for sc in swap_clients:
|
||||
sc._dleag_split_size_init = 9000
|
||||
sc._dleag_split_size = 11000
|
||||
sc._use_direct_message_routes = True
|
||||
|
||||
assert len(swap_clients[0].active_networks) == 1
|
||||
assert swap_clients[0].active_networks[0]["type"] == "simplex"
|
||||
|
||||
num_direct_messages_received_before = [0] * 3
|
||||
num_group_messages_received_before = [0] * 3
|
||||
for i in range(3):
|
||||
num_direct_messages_received_before[i] = swap_clients[
|
||||
i
|
||||
].num_direct_simplex_messages_received
|
||||
num_group_messages_received_before[i] = swap_clients[
|
||||
i
|
||||
].num_group_simplex_messages_received
|
||||
|
||||
coin_from = self.coin_to
|
||||
coin_to = Coins.BTC
|
||||
|
||||
ci_from = swap_clients[1].ci(coin_from)
|
||||
ci_to = swap_clients[0].ci(coin_to)
|
||||
|
||||
swap_value = ci_from.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
offer_id = swap_clients[1].postOffer(
|
||||
coin_from, coin_to, swap_value, rate_swap, swap_value, SwapTypes.XMR_SWAP
|
||||
)
|
||||
|
||||
wait_for_offer(test_delay_event, swap_clients[0], offer_id)
|
||||
offer = swap_clients[0].getOffer(offer_id)
|
||||
bid_id = swap_clients[0].postBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(
|
||||
test_delay_event,
|
||||
swap_clients[1],
|
||||
bid_id,
|
||||
BidStates.BID_RECEIVED,
|
||||
wait_for=60,
|
||||
)
|
||||
swap_clients[1].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid(
|
||||
test_delay_event,
|
||||
swap_clients[1],
|
||||
bid_id,
|
||||
BidStates.SWAP_COMPLETED,
|
||||
wait_for=320,
|
||||
)
|
||||
wait_for_bid(
|
||||
test_delay_event,
|
||||
swap_clients[0],
|
||||
bid_id,
|
||||
BidStates.SWAP_COMPLETED,
|
||||
sent=True,
|
||||
wait_for=320,
|
||||
)
|
||||
|
||||
for i in range(3):
|
||||
swap_clients[
|
||||
i
|
||||
].num_group_simplex_messages_received == num_group_messages_received_before[
|
||||
i
|
||||
] + 2
|
||||
swap_clients[
|
||||
2
|
||||
].num_direct_simplex_messages_received == num_direct_messages_received_before[2]
|
||||
|
||||
def test_03_hltc(self):
|
||||
logger.info("---------- Test secret hash swap")
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
for sc in swap_clients:
|
||||
sc._dleag_split_size_init = 9000
|
||||
sc._dleag_split_size = 11000
|
||||
sc._use_direct_message_routes = False
|
||||
|
||||
assert len(swap_clients[0].active_networks) == 1
|
||||
assert swap_clients[0].active_networks[0]["type"] == "simplex"
|
||||
|
||||
num_direct_messages_received_before = [0] * 3
|
||||
num_group_messages_received_before = [0] * 3
|
||||
for i in range(3):
|
||||
num_direct_messages_received_before[i] = swap_clients[
|
||||
i
|
||||
].num_direct_simplex_messages_received
|
||||
num_group_messages_received_before[i] = swap_clients[
|
||||
i
|
||||
].num_group_simplex_messages_received
|
||||
|
||||
coin_from = Coins.PART
|
||||
coin_to = Coins.BTC
|
||||
|
||||
self.prepare_balance(coin_to, 200.0, 1801, 1800)
|
||||
|
||||
ci_from = swap_clients[0].ci(coin_from)
|
||||
ci_to = swap_clients[1].ci(coin_to)
|
||||
|
||||
swap_value = ci_from.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
coin_from,
|
||||
coin_to,
|
||||
swap_value,
|
||||
rate_swap,
|
||||
swap_value,
|
||||
SwapTypes.SELLER_FIRST,
|
||||
)
|
||||
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(
|
||||
test_delay_event,
|
||||
swap_clients[0],
|
||||
bid_id,
|
||||
BidStates.BID_RECEIVED,
|
||||
wait_for=90,
|
||||
)
|
||||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid(
|
||||
test_delay_event,
|
||||
swap_clients[0],
|
||||
bid_id,
|
||||
BidStates.SWAP_COMPLETED,
|
||||
wait_for=320,
|
||||
)
|
||||
wait_for_bid(
|
||||
test_delay_event,
|
||||
swap_clients[1],
|
||||
bid_id,
|
||||
BidStates.SWAP_COMPLETED,
|
||||
sent=True,
|
||||
wait_for=320,
|
||||
)
|
||||
|
||||
for i in range(3):
|
||||
assert (
|
||||
num_direct_messages_received_before[i]
|
||||
== swap_clients[i].num_direct_simplex_messages_received
|
||||
)
|
||||
|
||||
def test_03_direct_hltc(self):
|
||||
logger.info("---------- Test secret hash swap with direct messages")
|
||||
|
||||
for i in range(3):
|
||||
message_routes = read_json_api(
|
||||
1800 + i, "messageroutes", {"action": "clear"}
|
||||
)
|
||||
assert len(message_routes) == 0
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
for sc in swap_clients:
|
||||
sc._dleag_split_size_init = 9000
|
||||
sc._dleag_split_size = 11000
|
||||
sc._use_direct_message_routes = True
|
||||
|
||||
assert len(swap_clients[0].active_networks) == 1
|
||||
assert swap_clients[0].active_networks[0]["type"] == "simplex"
|
||||
|
||||
num_direct_messages_received_before = [0] * 3
|
||||
num_group_messages_received_before = [0] * 3
|
||||
for i in range(3):
|
||||
num_direct_messages_received_before[i] = swap_clients[
|
||||
i
|
||||
].num_direct_simplex_messages_received
|
||||
num_group_messages_received_before[i] = swap_clients[
|
||||
i
|
||||
].num_group_simplex_messages_received
|
||||
|
||||
coin_from = Coins.PART
|
||||
coin_to = Coins.BTC
|
||||
|
||||
self.prepare_balance(coin_to, 200.0, 1801, 1800)
|
||||
|
||||
ci_from = swap_clients[0].ci(coin_from)
|
||||
ci_to = swap_clients[1].ci(coin_to)
|
||||
|
||||
swap_value = ci_from.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
coin_from,
|
||||
coin_to,
|
||||
swap_value,
|
||||
rate_swap,
|
||||
swap_value,
|
||||
SwapTypes.SELLER_FIRST,
|
||||
)
|
||||
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
bid_id = swap_clients[1].postBid(offer_id, offer.amount_from)
|
||||
|
||||
wait_for_bid(
|
||||
test_delay_event,
|
||||
swap_clients[0],
|
||||
bid_id,
|
||||
BidStates.BID_RECEIVED,
|
||||
wait_for=90,
|
||||
)
|
||||
swap_clients[0].acceptBid(bid_id)
|
||||
|
||||
wait_for_bid(
|
||||
test_delay_event,
|
||||
swap_clients[0],
|
||||
bid_id,
|
||||
BidStates.SWAP_COMPLETED,
|
||||
wait_for=320,
|
||||
)
|
||||
wait_for_bid(
|
||||
test_delay_event,
|
||||
swap_clients[1],
|
||||
bid_id,
|
||||
BidStates.SWAP_COMPLETED,
|
||||
sent=True,
|
||||
wait_for=320,
|
||||
)
|
||||
|
||||
message_routes = read_json_api(1800, "messageroutes")
|
||||
assert len(message_routes) == 1
|
||||
|
||||
for i in range(3):
|
||||
swap_clients[
|
||||
i
|
||||
].num_group_simplex_messages_received == num_group_messages_received_before[
|
||||
i
|
||||
] + 2
|
||||
swap_clients[
|
||||
2
|
||||
].num_direct_simplex_messages_received == num_direct_messages_received_before[2]
|
||||
|
||||
def test_04_multiple(self):
|
||||
logger.info("---------- Test multiple swaps with direct messages")
|
||||
|
||||
for i in range(3):
|
||||
message_routes = read_json_api(
|
||||
1800 + i, "messageroutes", {"action": "clear"}
|
||||
)
|
||||
assert len(message_routes) == 0
|
||||
|
||||
swap_clients = self.swap_clients
|
||||
|
||||
for sc in swap_clients:
|
||||
sc._dleag_split_size_init = 9000
|
||||
sc._dleag_split_size = 11000
|
||||
sc._use_direct_message_routes = True
|
||||
|
||||
assert len(swap_clients[0].active_networks) == 1
|
||||
assert swap_clients[0].active_networks[0]["type"] == "simplex"
|
||||
|
||||
num_direct_messages_received_before = [0] * 3
|
||||
num_group_messages_received_before = [0] * 3
|
||||
for i in range(3):
|
||||
num_direct_messages_received_before[i] = swap_clients[
|
||||
i
|
||||
].num_direct_simplex_messages_received
|
||||
num_group_messages_received_before[i] = swap_clients[
|
||||
i
|
||||
].num_group_simplex_messages_received
|
||||
|
||||
coin_from = Coins.BTC
|
||||
coin_to = self.coin_to
|
||||
|
||||
ci_from = swap_clients[0].ci(coin_from)
|
||||
ci_to = swap_clients[1].ci(coin_to)
|
||||
|
||||
swap_value = ci_from.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
rate_swap = ci_to.make_int(random.uniform(0.2, 20.0), r=1)
|
||||
offer_id = swap_clients[0].postOffer(
|
||||
coin_from, coin_to, swap_value, rate_swap, swap_value, SwapTypes.XMR_SWAP
|
||||
)
|
||||
|
||||
swap_clients[1].active_networks[0]["ws_thread"].ignore_events = True
|
||||
|
||||
wait_for_offer(test_delay_event, swap_clients[1], offer_id)
|
||||
offer = swap_clients[1].getOffer(offer_id)
|
||||
|
||||
addr1_bids = swap_clients[1].getReceiveAddressForCoin(Coins.PART)
|
||||
|
||||
bid_ids = []
|
||||
for i in range(2):
|
||||
bid_ids.append(
|
||||
swap_clients[1].postBid(
|
||||
offer_id, offer.amount_from, addr_send_from=addr1_bids
|
||||
)
|
||||
)
|
||||
|
||||
swap_clients[1].active_networks[0]["ws_thread"].disable_debug_mode()
|
||||
|
||||
bid_ids.append(swap_clients[1].postBid(offer_id, offer.amount_from))
|
||||
|
||||
for i in range(len(bid_ids)):
|
||||
wait_for_bid(
|
||||
test_delay_event,
|
||||
swap_clients[0],
|
||||
bid_ids[i],
|
||||
BidStates.BID_RECEIVED,
|
||||
wait_for=60,
|
||||
)
|
||||
swap_clients[0].acceptBid(bid_ids[i])
|
||||
|
||||
logger.info("Message routes with active bids shouldn't expire")
|
||||
swap_clients[0].mock_time_offset = (
|
||||
swap_clients[0]._expire_message_routes_after + 1
|
||||
)
|
||||
swap_clients[0].expireMessageRoutes()
|
||||
swap_clients[0].mock_time_offset = 0
|
||||
message_routes_0 = read_json_api(1800, "messageroutes")
|
||||
assert len(message_routes_0) == 2
|
||||
|
||||
for i in range(len(bid_ids)):
|
||||
wait_for_bid(
|
||||
test_delay_event,
|
||||
swap_clients[0],
|
||||
bid_ids[i],
|
||||
BidStates.SWAP_COMPLETED,
|
||||
wait_for=320,
|
||||
)
|
||||
wait_for_bid(
|
||||
test_delay_event,
|
||||
swap_clients[1],
|
||||
bid_ids[i],
|
||||
BidStates.SWAP_COMPLETED,
|
||||
sent=True,
|
||||
wait_for=320,
|
||||
)
|
||||
|
||||
for i in range(3):
|
||||
swap_clients[
|
||||
i
|
||||
].num_group_simplex_messages_received == num_group_messages_received_before[
|
||||
i
|
||||
] + 2
|
||||
swap_clients[
|
||||
2
|
||||
].num_direct_simplex_messages_received == num_direct_messages_received_before[2]
|
||||
|
||||
message_routes_0 = read_json_api(1800, "messageroutes")
|
||||
assert len(message_routes_0) == 2
|
||||
message_routes_1 = read_json_api(1801, "messageroutes")
|
||||
assert len(message_routes_1) == 2
|
||||
|
||||
logger.info("Test closing routes")
|
||||
read_json_api(1800, "messageroutes", {"action": "clear"})
|
||||
|
||||
def waitForNumMessageRoutes(
|
||||
port: int = 1800, num_routes: int = 0, num_tries: int = 40
|
||||
):
|
||||
logger.info(
|
||||
f"Waiting for {num_routes} message route{'s' if num_routes != 1 else ''}, port: {port}."
|
||||
)
|
||||
for i in range(num_tries):
|
||||
test_delay_event.wait(1)
|
||||
if test_delay_event.is_set():
|
||||
raise ValueError("Test stopped.")
|
||||
message_routes = read_json_api(port, "messageroutes")
|
||||
if len(message_routes) == num_routes:
|
||||
return True
|
||||
raise ValueError("waitForNumMessageRoutes timed out.")
|
||||
|
||||
waitForNumMessageRoutes(1800, 0)
|
||||
waitForNumMessageRoutes(1801, 0)
|
||||
|
||||
@@ -87,6 +87,9 @@ TEST_COINS_LIST = os.getenv("TEST_COINS_LIST", "bitcoin,monero")
|
||||
NUM_NODES = int(os.getenv("NUM_NODES", 3))
|
||||
EXTRA_CONFIG_JSON = json.loads(os.getenv("EXTRA_CONFIG_JSON", "{}"))
|
||||
|
||||
SIMPLEX_SERVER_ADDRESS = os.getenv("SIMPLEX_SERVER_ADDRESS", "")
|
||||
SIMPLEX_CLIENT_PATH = os.path.expanduser(os.getenv("SIMPLEX_CLIENT_PATH", ""))
|
||||
|
||||
logger = logging.getLogger()
|
||||
logger.level = logging.DEBUG
|
||||
if not len(logger.handlers):
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Copyright (c) 2021-2022 tecnovert
|
||||
# Copyright (c) 2024 The Basicswap developers
|
||||
# Copyright (c) 2024-2025 The Basicswap developers
|
||||
# Distributed under the MIT software license, see the accompanying
|
||||
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
|
||||
|
||||
@@ -17,12 +17,9 @@ python tests/basicswap/test_xmr_bids_offline.py
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import logging
|
||||
import unittest
|
||||
import multiprocessing
|
||||
from urllib import parse
|
||||
from urllib.request import urlopen
|
||||
|
||||
from tests.basicswap.util import (
|
||||
read_json_api,
|
||||
@@ -64,21 +61,11 @@ class Test(XmrTestBase):
|
||||
"lockhrs": 24,
|
||||
"automation_strat_id": 1,
|
||||
}
|
||||
rv = json.loads(
|
||||
urlopen(
|
||||
"http://127.0.0.1:12700/json/offers/new",
|
||||
data=parse.urlencode(offer_data).encode(),
|
||||
).read()
|
||||
)
|
||||
rv = read_json_api(12700, "offers/new", offer_data)
|
||||
offer0_id = rv["offer_id"]
|
||||
|
||||
offer_data["amt_from"] = "2"
|
||||
rv = json.loads(
|
||||
urlopen(
|
||||
"http://127.0.0.1:12700/json/offers/new",
|
||||
data=parse.urlencode(offer_data).encode(),
|
||||
).read()
|
||||
)
|
||||
rv = read_json_api(12700, "offers/new", offer_data)
|
||||
offer1_id = rv["offer_id"]
|
||||
|
||||
summary = read_json_api(12700)
|
||||
@@ -92,52 +79,26 @@ class Test(XmrTestBase):
|
||||
c0.terminate()
|
||||
c0.join()
|
||||
|
||||
offers = json.loads(
|
||||
urlopen("http://127.0.0.1:12701/json/offers/{}".format(offer0_id)).read()
|
||||
)
|
||||
offers = read_json_api(12701, f"offers/{offer0_id}")
|
||||
assert len(offers) == 1
|
||||
offer0 = offers[0]
|
||||
|
||||
post_data = {"coin_from": "PART"}
|
||||
test_post_offers = json.loads(
|
||||
urlopen(
|
||||
"http://127.0.0.1:12701/json/offers",
|
||||
data=parse.urlencode(post_data).encode(),
|
||||
).read()
|
||||
)
|
||||
test_post_offers = read_json_api(12701, "offers", post_data)
|
||||
assert len(test_post_offers) == 2
|
||||
post_data["coin_from"] = "2"
|
||||
test_post_offers = json.loads(
|
||||
urlopen(
|
||||
"http://127.0.0.1:12701/json/offers",
|
||||
data=parse.urlencode(post_data).encode(),
|
||||
).read()
|
||||
)
|
||||
test_post_offers = read_json_api(12701, "offers", post_data)
|
||||
assert len(test_post_offers) == 0
|
||||
|
||||
bid_data = {"offer_id": offer0_id, "amount_from": offer0["amount_from"]}
|
||||
bid0_id = read_json_api(12701, "bids/new", bid_data)["bid_id"]
|
||||
|
||||
bid0_id = json.loads(
|
||||
urlopen(
|
||||
"http://127.0.0.1:12701/json/bids/new",
|
||||
data=parse.urlencode(bid_data).encode(),
|
||||
).read()
|
||||
)["bid_id"]
|
||||
|
||||
offers = json.loads(
|
||||
urlopen("http://127.0.0.1:12701/json/offers/{}".format(offer1_id)).read()
|
||||
)
|
||||
offers = read_json_api(12701, f"offers/{offer1_id}")
|
||||
assert len(offers) == 1
|
||||
offer1 = offers[0]
|
||||
|
||||
bid_data = {"offer_id": offer1_id, "amount_from": offer1["amount_from"]}
|
||||
|
||||
bid1_id = json.loads(
|
||||
urlopen(
|
||||
"http://127.0.0.1:12701/json/bids/new",
|
||||
data=parse.urlencode(bid_data).encode(),
|
||||
).read()
|
||||
)["bid_id"]
|
||||
bid1_id = read_json_api(12701, "bids/new", bid_data)["bid_id"]
|
||||
|
||||
logger.info("Delaying for 5 seconds.")
|
||||
self.delay_event.wait(5)
|
||||
@@ -149,26 +110,17 @@ class Test(XmrTestBase):
|
||||
waitForServer(self.delay_event, 12700)
|
||||
waitForNumBids(self.delay_event, 12700, 2)
|
||||
|
||||
waitForBidState(self.delay_event, 12700, bid0_id, "Received")
|
||||
waitForBidState(self.delay_event, 12700, bid1_id, "Received")
|
||||
waitForBidState(self.delay_event, 12700, bid0_id, ("Received", "Delaying"))
|
||||
waitForBidState(self.delay_event, 12700, bid1_id, ("Received", "Delaying"))
|
||||
|
||||
# Manually accept on top of auto-accept for extra chaos
|
||||
data = parse.urlencode({"accept": True}).encode()
|
||||
try:
|
||||
rv = json.loads(
|
||||
urlopen(
|
||||
"http://127.0.0.1:12700/json/bids/{}".format(bid0_id), data=data
|
||||
).read()
|
||||
)
|
||||
rv = read_json_api(12700, f"bids/{bid0_id}", {"accept": True})
|
||||
assert rv["bid_state"] == "Accepted"
|
||||
except Exception as e:
|
||||
print("Accept bid failed", str(e), rv)
|
||||
try:
|
||||
rv = json.loads(
|
||||
urlopen(
|
||||
"http://127.0.0.1:12700/json/bids/{}".format(bid1_id), data=data
|
||||
).read()
|
||||
)
|
||||
rv = read_json_api(12700, f"bids/{bid1_id}", {"accept": True})
|
||||
assert rv["bid_state"] == "Accepted"
|
||||
except Exception as e:
|
||||
print("Accept bid failed", str(e), rv)
|
||||
@@ -179,8 +131,8 @@ class Test(XmrTestBase):
|
||||
raise ValueError("Test stopped.")
|
||||
self.delay_event.wait(4)
|
||||
|
||||
rv0 = read_json_api(12700, "bids/{}".format(bid0_id))
|
||||
rv1 = read_json_api(12700, "bids/{}".format(bid1_id))
|
||||
rv0 = read_json_api(12700, f"bids/{bid0_id}")
|
||||
rv1 = read_json_api(12700, f"bids/{bid1_id}")
|
||||
if rv0["bid_state"] == "Completed" and rv1["bid_state"] == "Completed":
|
||||
break
|
||||
assert rv0["bid_state"] == "Completed"
|
||||
|
||||
Reference in New Issue
Block a user