2024-06-11 11:36:20 +00:00
|
|
|
import json
|
2024-09-16 08:23:57 +00:00
|
|
|
import websocket
|
|
|
|
import threading
|
|
|
|
import logging
|
2024-08-13 12:46:48 +00:00
|
|
|
import jsonschema
|
2024-09-25 12:27:04 +00:00
|
|
|
import time
|
2024-06-11 11:36:20 +00:00
|
|
|
import requests
|
|
|
|
from conftest import option, user_1, user_2
|
2024-10-04 12:55:28 +00:00
|
|
|
import pytest
|
|
|
|
from collections import namedtuple
|
2024-06-11 11:36:20 +00:00
|
|
|
|
|
|
|
class RpcTestCase:
|
2024-10-04 12:55:28 +00:00
|
|
|
network_id = 31337
|
2024-06-11 11:36:20 +00:00
|
|
|
|
|
|
|
def setup_method(self):
|
2024-10-04 12:55:28 +00:00
|
|
|
pass
|
2024-06-11 11:36:20 +00:00
|
|
|
|
2024-09-18 16:54:17 +00:00
|
|
|
def _try_except_JSONDecodeError_KeyError(self, response, key: str):
|
|
|
|
try:
|
|
|
|
return response.json()[key]
|
|
|
|
except json.JSONDecodeError:
|
2024-09-25 12:27:04 +00:00
|
|
|
raise AssertionError(
|
|
|
|
f"Invalid JSON in response: {response.content}")
|
2024-09-18 16:54:17 +00:00
|
|
|
except KeyError:
|
2024-09-25 12:27:04 +00:00
|
|
|
raise AssertionError(
|
|
|
|
f"Key '{key}' not found in the JSON response: {response.content}")
|
2024-09-18 16:54:17 +00:00
|
|
|
|
2024-06-11 11:36:20 +00:00
|
|
|
def verify_is_valid_json_rpc_response(self, response, _id=None):
|
|
|
|
assert response.status_code == 200
|
|
|
|
assert response.content
|
2024-09-18 16:54:17 +00:00
|
|
|
self._try_except_JSONDecodeError_KeyError(response, "result")
|
2024-06-11 11:36:20 +00:00
|
|
|
|
|
|
|
if _id:
|
|
|
|
try:
|
|
|
|
if _id != response.json()["id"]:
|
|
|
|
raise AssertionError(
|
|
|
|
f"got id: {response.json()['id']} instead of expected id: {_id}"
|
|
|
|
)
|
|
|
|
except KeyError:
|
|
|
|
raise AssertionError(f"no id in response {response.json()}")
|
|
|
|
return response
|
|
|
|
|
2024-09-18 16:54:17 +00:00
|
|
|
def verify_is_json_rpc_error(self, response):
|
|
|
|
assert response.status_code == 200
|
|
|
|
assert response.content
|
|
|
|
self._try_except_JSONDecodeError_KeyError(response, "error")
|
|
|
|
|
2024-08-13 12:46:48 +00:00
|
|
|
def rpc_request(self, method, params=[], _id=None, client=None, url=None):
|
|
|
|
client = client if client else requests.Session()
|
|
|
|
url = url if url else option.rpc_url
|
2024-06-11 11:36:20 +00:00
|
|
|
|
|
|
|
data = {"jsonrpc": "2.0", "method": method}
|
|
|
|
if params:
|
|
|
|
data["params"] = params
|
|
|
|
data["id"] = _id if _id else 13
|
|
|
|
|
2024-08-13 12:46:48 +00:00
|
|
|
response = client.post(url, json=data)
|
2024-06-11 11:36:20 +00:00
|
|
|
|
|
|
|
return response
|
|
|
|
|
2024-10-04 12:55:28 +00:00
|
|
|
def rpc_valid_request(self, method, params=[], _id=None, client=None, url=None):
|
|
|
|
response = self.rpc_request(method, params, _id, client, url)
|
|
|
|
self.verify_is_valid_json_rpc_response(response, _id)
|
|
|
|
return response
|
|
|
|
|
2024-08-13 12:46:48 +00:00
|
|
|
def verify_json_schema(self, response, method):
|
|
|
|
with open(f"{option.base_dir}/schemas/{method}", "r") as schema:
|
2024-09-16 08:23:57 +00:00
|
|
|
jsonschema.validate(instance=response.json(),
|
|
|
|
schema=json.load(schema))
|
|
|
|
|
2024-10-04 12:55:28 +00:00
|
|
|
class WalletTestCase(RpcTestCase):
|
|
|
|
def setup_method(self):
|
|
|
|
super().setup_method()
|
2024-06-11 11:36:20 +00:00
|
|
|
|
2024-09-18 16:54:17 +00:00
|
|
|
def wallet_create_multi_transaction(self, **kwargs):
|
2024-06-11 11:36:20 +00:00
|
|
|
method = "wallet_createMultiTransaction"
|
2024-09-18 16:54:17 +00:00
|
|
|
transferTx_data = {
|
2024-09-25 12:27:04 +00:00
|
|
|
"data": "",
|
|
|
|
"from": user_1.address,
|
|
|
|
"gas": "0x5BBF",
|
|
|
|
"input": "",
|
|
|
|
"maxFeePerGas": "0xbcc0f04fd",
|
|
|
|
"maxPriorityFeePerGas": "0xbcc0f04fd",
|
|
|
|
"to": user_2.address,
|
|
|
|
"type": "0x02",
|
|
|
|
"value": "0x5af3107a4000",
|
|
|
|
}
|
2024-09-18 16:54:17 +00:00
|
|
|
for key, new_value in kwargs.items():
|
|
|
|
if key in transferTx_data:
|
|
|
|
transferTx_data[key] = new_value
|
|
|
|
else:
|
2024-09-25 12:27:04 +00:00
|
|
|
print(
|
|
|
|
f"Warning: The key '{key}' does not exist in the transferTx parameters and will be ignored.")
|
2024-06-11 11:36:20 +00:00
|
|
|
params = [
|
|
|
|
{
|
|
|
|
"fromAddress": user_1.address,
|
|
|
|
"fromAmount": "0x5af3107a4000",
|
|
|
|
"fromAsset": "ETH",
|
2024-10-04 12:55:28 +00:00
|
|
|
"type": 0, # MultiTransactionSend
|
2024-06-11 11:36:20 +00:00
|
|
|
"toAddress": user_2.address,
|
|
|
|
"toAsset": "ETH",
|
|
|
|
},
|
|
|
|
[
|
|
|
|
{
|
|
|
|
"bridgeName": "Transfer",
|
|
|
|
"chainID": 31337,
|
2024-09-18 16:54:17 +00:00
|
|
|
"transferTx": transferTx_data
|
2024-06-11 11:36:20 +00:00
|
|
|
}
|
|
|
|
],
|
|
|
|
f"{option.password}",
|
|
|
|
]
|
2024-10-04 12:55:28 +00:00
|
|
|
return self.rpc_request(method, params)
|
|
|
|
|
|
|
|
def send_valid_multi_transaction(self, **kwargs):
|
|
|
|
response = self.wallet_create_multi_transaction(**kwargs)
|
2024-06-11 11:36:20 +00:00
|
|
|
|
2024-10-04 12:55:28 +00:00
|
|
|
tx_hash = None
|
|
|
|
self.verify_is_valid_json_rpc_response(response)
|
|
|
|
try:
|
|
|
|
tx_hash = response.json(
|
|
|
|
)["result"]["hashes"][str(self.network_id)][0]
|
|
|
|
except (KeyError, json.JSONDecodeError):
|
|
|
|
raise Exception(response.content)
|
|
|
|
return tx_hash
|
|
|
|
|
|
|
|
class TransactionTestCase(WalletTestCase):
|
2024-06-11 11:36:20 +00:00
|
|
|
def setup_method(self):
|
|
|
|
super().setup_method()
|
2024-09-16 08:23:57 +00:00
|
|
|
|
2024-10-04 12:55:28 +00:00
|
|
|
self.tx_hash = self.send_valid_multi_transaction()
|
|
|
|
|
|
|
|
class EthApiTestCase(WalletTestCase):
|
|
|
|
@pytest.fixture(autouse=True, scope='class')
|
|
|
|
def tx_data(self):
|
|
|
|
tx_hash = self.send_valid_multi_transaction()
|
|
|
|
self.wait_until_tx_not_pending(tx_hash)
|
|
|
|
|
|
|
|
receipt = self.get_transaction_receipt(tx_hash)
|
2024-06-11 11:36:20 +00:00
|
|
|
try:
|
2024-10-04 12:55:28 +00:00
|
|
|
block_number = receipt.json()["result"]["blockNumber"]
|
|
|
|
block_hash = receipt.json()["result"]["blockHash"]
|
2024-06-11 11:36:20 +00:00
|
|
|
except (KeyError, json.JSONDecodeError):
|
2024-10-04 12:55:28 +00:00
|
|
|
raise Exception(receipt.content)
|
|
|
|
|
|
|
|
TxData = namedtuple("TxData", ["tx_hash", "block_number", "block_hash"])
|
|
|
|
return TxData(tx_hash, block_number, block_hash)
|
|
|
|
|
|
|
|
def get_block_header(self, block_number):
|
|
|
|
method = "ethclient_headerByNumber"
|
|
|
|
params = [self.network_id, block_number]
|
|
|
|
return self.rpc_valid_request(method, params)
|
|
|
|
|
|
|
|
def get_transaction_receipt(self, tx_hash):
|
|
|
|
method = "ethclient_transactionReceipt"
|
|
|
|
params = [self.network_id, tx_hash]
|
|
|
|
return self.rpc_valid_request(method, params)
|
|
|
|
|
|
|
|
def wait_until_tx_not_pending(self, tx_hash, timeout=10):
|
|
|
|
method = "ethclient_transactionByHash"
|
|
|
|
params = [self.network_id, tx_hash]
|
|
|
|
response = self.rpc_valid_request(method, params)
|
2024-06-11 11:36:20 +00:00
|
|
|
|
2024-10-04 12:55:28 +00:00
|
|
|
start_time = time.time()
|
|
|
|
while response.json()["result"]["isPending"] == True:
|
|
|
|
time_passed = time.time() - start_time
|
|
|
|
if time_passed >= timeout:
|
|
|
|
raise TimeoutError(
|
|
|
|
f"Tx {tx_hash} is still pending after {timeout} seconds")
|
|
|
|
time.sleep(0.5)
|
|
|
|
response = self.rpc_valid_request(method, params)
|
|
|
|
return response.json()["result"]["tx"]
|
2024-09-16 08:23:57 +00:00
|
|
|
|
|
|
|
class SignalTestCase(RpcTestCase):
|
|
|
|
|
2024-09-25 12:27:04 +00:00
|
|
|
await_signals = []
|
|
|
|
received_signals = {}
|
|
|
|
|
|
|
|
def on_message(self, ws, signal):
|
|
|
|
signal = json.loads(signal)
|
|
|
|
if signal.get("type") in self.await_signals:
|
|
|
|
self.received_signals[signal["type"]] = signal
|
2024-09-16 08:23:57 +00:00
|
|
|
|
2024-09-25 12:27:04 +00:00
|
|
|
def wait_for_signal(self, signal_type, timeout=10):
|
|
|
|
start_time = time.time()
|
|
|
|
while signal_type not in self.received_signals:
|
|
|
|
time_passed = time.time() - start_time
|
|
|
|
if time_passed >= timeout:
|
|
|
|
raise TimeoutError(
|
|
|
|
f"Signal {signal_type} is not received in {timeout} seconds")
|
|
|
|
time.sleep(0.5)
|
|
|
|
return self.received_signals[signal_type]
|
2024-09-16 08:23:57 +00:00
|
|
|
|
|
|
|
def _on_error(self, ws, error):
|
|
|
|
logging.info(f"Error: {error}")
|
|
|
|
|
|
|
|
def _on_close(self, ws, close_status_code, close_msg):
|
|
|
|
logging.info(f"Connection closed: {close_status_code}, {close_msg}")
|
|
|
|
|
|
|
|
def _on_open(self, ws):
|
|
|
|
logging.info("Connection opened")
|
|
|
|
|
|
|
|
def _connect(self):
|
|
|
|
self.url = f"{option.ws_url}/signals"
|
|
|
|
|
|
|
|
ws = websocket.WebSocketApp(self.url,
|
2024-09-25 12:27:04 +00:00
|
|
|
on_message=self.on_message,
|
2024-09-16 08:23:57 +00:00
|
|
|
on_error=self._on_error,
|
|
|
|
on_close=self._on_close)
|
|
|
|
|
|
|
|
ws.on_open = self._on_open
|
|
|
|
|
|
|
|
ws.run_forever()
|
|
|
|
|
|
|
|
def setup_method(self):
|
|
|
|
super().setup_method()
|
|
|
|
|
|
|
|
websocket_thread = threading.Thread(target=self._connect)
|
|
|
|
websocket_thread.daemon = True
|
|
|
|
websocket_thread.start()
|