From def5f0c04867b5d4c46343cd417662a40cc19f93 Mon Sep 17 00:00:00 2001 From: jaypan Date: Fri, 13 Dec 2024 08:57:33 +0100 Subject: [PATCH 1/6] Add the weight/fee on substrate side --- .gitignore | 1 + tests/conftest.py | 108 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) diff --git a/.gitignore b/.gitignore index 12330bb..3662ec2 100644 --- a/.gitignore +++ b/.gitignore @@ -171,3 +171,4 @@ Sessionx.vim tags # Persistent undo [._]*.un~ +reports/ diff --git a/tests/conftest.py b/tests/conftest.py index a89f69f..45c0286 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,6 +2,83 @@ import importlib import sys import tools.utils # noqa: F401 +from substrateinterface import SubstrateInterface +import os +import datetime +import json + +old_submit_extrinsic = SubstrateInterface.submit_extrinsic +all_weight_fee_data = {} + + +def compose_extrinsic_name(call): + module = call['call_module'] + function = call['call_function'] + extrinsic_name = f'{module}.{function}' + if module != 'Utility' and function != 'batch_all': + return extrinsic_name + + inner_names = [compose_extrinsic_name(call) for call in call['call_args'][0]['value']] + extrinsic_name += f'({", ".join(inner_names)})' + + return extrinsic_name + + +def get_transaction_fee(substrate, extrinsic_idx, block_hash): + events = substrate.get_events(block_hash) + for event in events: + if event.value['extrinsic_idx'] != extrinsic_idx: + continue + if event.value['module_id'] != 'TransactionPayment' or \ + event.value['event_id'] != 'TransactionFeePaid': + continue + return event['event'][1][1].value['actual_fee'] - event['event'][1][1].value['tip'] + + +def get_transaction_weight(substrate, extrinsic_idx, block_hash): + events = substrate.get_events(block_hash) + + for event in events: + if event.value['extrinsic_idx'] != extrinsic_idx: + continue + if event.value['module_id'] != 'System' or \ + event.value['phase'] != 'ApplyExtrinsic' or \ + 'dispatch_info' not in event.value['attributes']: + continue + # We didn't check event_id because it might be extrnisic error + return event.value['attributes']['dispatch_info']['weight']['ref_time'] + + +def monkey_submit_extrinsic_for_fee_weight(self, extrinsic, wait_for_inclusion, wait_for_finalization): + receipt = old_submit_extrinsic(self, extrinsic, wait_for_inclusion, wait_for_finalization) + + block_hash = receipt.block_hash + block = self.get_block(block_hash) + for extrinsic in block['extrinsics']: + if f'0x{extrinsic.extrinsic_hash.hex()}' != receipt.extrinsic_hash: + continue + name = compose_extrinsic_name(extrinsic.value['call']) + fee = get_transaction_fee(self, receipt.extrinsic_idx, block_hash) + weight = get_transaction_weight(self, receipt.extrinsic_idx, block_hash) + if name in all_weight_fee_data: + all_weight_fee_data[name].append({ + 'fee': fee, + 'weight': weight + }) + else: + all_weight_fee_data[name] = [{ + 'fee': fee, + 'weight': weight + }] + + break + else: + raise Exception('Extrinsic not found in the block') + + return receipt + + +SubstrateInterface.submit_extrinsic = monkey_submit_extrinsic_for_fee_weight def pytest_runtest_setup(item): @@ -11,3 +88,34 @@ def pytest_runtest_setup(item): importlib.reload(sys.modules['substrateinterface']) if 'peaq.utils' in sys.modules: importlib.reload(sys.modules['peaq.utils']) + + +def process_weight_fee_data(): + summary_data = {} + for extrinsic_name, data in all_weight_fee_data.items(): + summary_data[extrinsic_name] = { + 'fee': sum([d['fee'] for d in data]) / len(data), + 'weight': sum([d['weight'] for d in data]) / len(data), + 'len': len(data) + } + return summary_data + + +def pytest_sessionfinish(session, exitstatus): + # get date by format "YYYY-MM-DD-HH-MM" + now = datetime.datetime.now() + date = now.strftime("%Y-%m-%d-%H-%M") + + folder = "reports" + report_file = f"weight_fee_summary_{date}.json" + + if not os.path.exists(folder): + os.makedirs(folder) + + report_path = os.path.join(folder, report_file) + summary_data = process_weight_fee_data() + with open(report_path, "w") as f: + json.dump(summary_data, f, indent=4) + + print('') + print(f"Weight/fee data saved to {report_path}") From e01a6e98955432c6db2ef0c574e4964b0a08e720 Mon Sep 17 00:00:00 2001 From: jaypan Date: Sat, 14 Dec 2024 11:39:40 +0100 Subject: [PATCH 2/6] Use the new evm fee collection func to execute the transaction --- ETH/did/{did.sol.json => abi} | 0 ETH/{erc20/plus.abi => erc20plus/abi} | 0 ETH/rbac/{rbac.sol.json => abi} | 0 ETH/storage/{storage.sol.json => abi} | 0 tests/bridge_asset_factory_test.py | 4 +- tests/bridge_balance_erc20_test.py | 2 +- tests/bridge_batch_test.py | 6 +- tests/bridge_did_test.py | 4 +- tests/bridge_erc20_test.py | 2 +- tests/bridge_multiple_collator_test.py | 2 +- tests/bridge_parachain_staking_test.py | 2 +- tests/bridge_rbac_test.py | 4 +- tests/bridge_storage_test.py | 4 +- tests/bridge_vest_test.py | 2 +- tests/bridge_xcmutils_test.py | 2 +- tests/bridge_xtokens_test.py | 2 +- tests/conftest.py | 108 ++--------------------- tests/evm_eth_rpc_test.py | 2 +- tests/evm_utils.py | 113 +++++++++++++++++++++++++ tests/pallet_address_unification.py | 2 +- tests/pallet_rbac_test.py | 2 + tests/substrate_utils.py | 110 ++++++++++++++++++++++++ tools/uniswap_test_setup.py | 2 +- 23 files changed, 252 insertions(+), 123 deletions(-) rename ETH/did/{did.sol.json => abi} (100%) rename ETH/{erc20/plus.abi => erc20plus/abi} (100%) rename ETH/rbac/{rbac.sol.json => abi} (100%) rename ETH/storage/{storage.sol.json => abi} (100%) create mode 100644 tests/evm_utils.py create mode 100644 tests/substrate_utils.py diff --git a/ETH/did/did.sol.json b/ETH/did/abi similarity index 100% rename from ETH/did/did.sol.json rename to ETH/did/abi diff --git a/ETH/erc20/plus.abi b/ETH/erc20plus/abi similarity index 100% rename from ETH/erc20/plus.abi rename to ETH/erc20plus/abi diff --git a/ETH/rbac/rbac.sol.json b/ETH/rbac/abi similarity index 100% rename from ETH/rbac/rbac.sol.json rename to ETH/rbac/abi diff --git a/ETH/storage/storage.sol.json b/ETH/storage/abi similarity index 100% rename from ETH/storage/storage.sol.json rename to ETH/storage/abi diff --git a/tests/bridge_asset_factory_test.py b/tests/bridge_asset_factory_test.py index 242e03c..717bb59 100644 --- a/tests/bridge_asset_factory_test.py +++ b/tests/bridge_asset_factory_test.py @@ -10,7 +10,7 @@ from tools.peaq_eth_utils import calculate_asset_to_evm_address from tools.peaq_eth_utils import get_eth_info from tools.constants import KP_GLOBAL_SUDO -from tools.peaq_eth_utils import sign_and_submit_evm_transaction +from tests.evm_utils import sign_and_submit_evm_transaction from web3 import Web3 @@ -20,7 +20,7 @@ BATCH_ABI_FILE = 'ETH/batch/abi' BATCH_ADDRESS = '0x0000000000000000000000000000000000000805' -IERC20PLUS_ABI_FILE = 'ETH/erc20/plus.abi' +IERC20PLUS_ABI_FILE = 'ETH/erc20plus/abi' @pytest.mark.eth diff --git a/tests/bridge_balance_erc20_test.py b/tests/bridge_balance_erc20_test.py index 55727da..ddd123f 100644 --- a/tests/bridge_balance_erc20_test.py +++ b/tests/bridge_balance_erc20_test.py @@ -5,7 +5,7 @@ from peaq.utils import ExtrinsicBatch from peaq.utils import get_account_balance from tools.utils import get_modified_chain_spec -from tools.peaq_eth_utils import sign_and_submit_evm_transaction +from tests.evm_utils import sign_and_submit_evm_transaction from tools.constants import WS_URL, ETH_URL from tools.constants import KP_GLOBAL_SUDO from tools.peaq_eth_utils import get_contract diff --git a/tests/bridge_batch_test.py b/tests/bridge_batch_test.py index 88525ac..0e8174a 100644 --- a/tests/bridge_batch_test.py +++ b/tests/bridge_batch_test.py @@ -10,7 +10,7 @@ from peaq.utils import ExtrinsicBatch from web3 import Web3 from tools.constants import KP_GLOBAL_SUDO -from tools.peaq_eth_utils import sign_and_submit_evm_transaction +from tests.evm_utils import sign_and_submit_evm_transaction from tools.peaq_eth_utils import generate_random_hex @@ -22,10 +22,10 @@ ABI_FILE = 'ETH/batch/abi' BATCH_ADDRESS = '0x0000000000000000000000000000000000000805' -DID_ABI_FILE = 'ETH/did/did.sol.json' +DID_ABI_FILE = 'ETH/did/abi' DID_ADDRESS = '0x0000000000000000000000000000000000000800' -STORAGE_ABI_FILE = 'ETH/storage/storage.sol.json' +STORAGE_ABI_FILE = 'ETH/storage/abi' STORAGE_ADDRESS = '0x0000000000000000000000000000000000000801' diff --git a/tests/bridge_did_test.py b/tests/bridge_did_test.py index 5da3dff..1d5b407 100644 --- a/tests/bridge_did_test.py +++ b/tests/bridge_did_test.py @@ -6,7 +6,7 @@ from peaq.extrinsic import transfer from tools.peaq_eth_utils import call_eth_transfer_a_lot, get_contract, generate_random_hex from tools.constants import WS_URL, ETH_URL -from tools.peaq_eth_utils import sign_and_submit_evm_transaction +from tests.evm_utils import sign_and_submit_evm_transaction from tools.peaq_eth_utils import get_eth_chain_id from web3 import Web3 @@ -21,7 +21,7 @@ DID_ADDRESS = '0x0000000000000000000000000000000000000800' ETH_PRIVATE_KEY = '0xa2899b053679427c8c446dc990c8990c75052fd3009e563c6a613d982d6842fe' VALIDITY = 1000 -ABI_FILE = 'ETH/did/did.sol.json' +ABI_FILE = 'ETH/did/abi' @pytest.mark.eth diff --git a/tests/bridge_erc20_test.py b/tests/bridge_erc20_test.py index 2fada8a..f5a8e12 100644 --- a/tests/bridge_erc20_test.py +++ b/tests/bridge_erc20_test.py @@ -3,7 +3,7 @@ from substrateinterface import SubstrateInterface, Keypair from tools.asset import batch_create_asset, get_valid_asset_id, batch_set_metadata, batch_mint from tools.constants import WS_URL, ETH_URL -from tools.peaq_eth_utils import sign_and_submit_evm_transaction +from tests.evm_utils import sign_and_submit_evm_transaction from peaq.utils import ExtrinsicBatch from tools.peaq_eth_utils import get_contract from tools.peaq_eth_utils import get_eth_chain_id diff --git a/tests/bridge_multiple_collator_test.py b/tests/bridge_multiple_collator_test.py index c29befd..d19df30 100644 --- a/tests/bridge_multiple_collator_test.py +++ b/tests/bridge_multiple_collator_test.py @@ -4,7 +4,7 @@ from tools.runtime_upgrade import wait_until_block_height from substrateinterface import SubstrateInterface, Keypair from tools.constants import WS_URL, ETH_URL, RELAYCHAIN_WS_URL -from tools.peaq_eth_utils import sign_and_submit_evm_transaction +from tests.evm_utils import sign_and_submit_evm_transaction from peaq.utils import ExtrinsicBatch from tools.peaq_eth_utils import get_contract from tools.peaq_eth_utils import get_eth_chain_id diff --git a/tests/bridge_parachain_staking_test.py b/tests/bridge_parachain_staking_test.py index 617e58b..6d078aa 100644 --- a/tests/bridge_parachain_staking_test.py +++ b/tests/bridge_parachain_staking_test.py @@ -4,7 +4,7 @@ from tools.runtime_upgrade import wait_until_block_height from substrateinterface import SubstrateInterface, Keypair from tools.constants import WS_URL, ETH_URL, RELAYCHAIN_WS_URL -from tools.peaq_eth_utils import sign_and_submit_evm_transaction +from tests.evm_utils import sign_and_submit_evm_transaction from peaq.utils import ExtrinsicBatch from tests import utils_func as TestUtils from tools.peaq_eth_utils import get_contract diff --git a/tests/bridge_rbac_test.py b/tests/bridge_rbac_test.py index 6f0798b..cb1274c 100644 --- a/tests/bridge_rbac_test.py +++ b/tests/bridge_rbac_test.py @@ -1,7 +1,7 @@ import pytest from substrateinterface import SubstrateInterface, Keypair, KeypairType -from tools.peaq_eth_utils import sign_and_submit_evm_transaction +from tests.evm_utils import sign_and_submit_evm_transaction from tools.constants import WS_URL, ETH_URL from peaq.eth import calculate_evm_account, calculate_evm_addr from tools.peaq_eth_utils import get_eth_chain_id @@ -22,7 +22,7 @@ # H160 Address to use for EVM transactions ETH_PRIVATE_KEY = generate_random_hex(15).encode("utf-8") # RBAC Precompile ABI -ABI_FILE = 'ETH/rbac/rbac.sol.json' +ABI_FILE = 'ETH/rbac/abi' # Number of tokens with decimals TOKEN_NUM = 10000 * pow(10, 15) diff --git a/tests/bridge_storage_test.py b/tests/bridge_storage_test.py index fd998fa..0920961 100644 --- a/tests/bridge_storage_test.py +++ b/tests/bridge_storage_test.py @@ -4,7 +4,7 @@ from peaq.eth import calculate_evm_account_hex, calculate_evm_addr, calculate_evm_account from peaq.extrinsic import transfer from tools.constants import WS_URL, ETH_URL -from tools.peaq_eth_utils import sign_and_submit_evm_transaction +from tests.evm_utils import sign_and_submit_evm_transaction from tools.peaq_eth_utils import get_eth_chain_id from tools.peaq_eth_utils import call_eth_transfer_a_lot, get_contract, generate_random_hex from tools.peaq_eth_utils import TX_SUCCESS_STATUS @@ -24,7 +24,7 @@ KP_SRC = Keypair.create_from_uri('//Alice') STORAGE_ADDRESS = '0x0000000000000000000000000000000000000801' ETH_PRIVATE_KEY = '0xa2899b053679427c8c446dc990c8990c75052fd3009e563c6a613d982d6842fe' -ABI_FILE = 'ETH/storage/storage.sol.json' +ABI_FILE = 'ETH/storage/abi' TOKEN_NUM = 10000 * pow(10, 15) diff --git a/tests/bridge_vest_test.py b/tests/bridge_vest_test.py index 1c7a39c..ae2e480 100644 --- a/tests/bridge_vest_test.py +++ b/tests/bridge_vest_test.py @@ -9,7 +9,7 @@ from tools.constants import KP_GLOBAL_SUDO from tools.constants import WS_URL, ETH_URL from tools.utils import get_account_balance_locked -from tools.peaq_eth_utils import sign_and_submit_evm_transaction +from tests.evm_utils import sign_and_submit_evm_transaction from tools.peaq_eth_utils import get_eth_chain_id from tools.peaq_eth_utils import get_eth_info from tools.utils import batch_fund diff --git a/tests/bridge_xcmutils_test.py b/tests/bridge_xcmutils_test.py index 470b1eb..c48496d 100644 --- a/tests/bridge_xcmutils_test.py +++ b/tests/bridge_xcmutils_test.py @@ -10,7 +10,7 @@ from peaq.utils import ExtrinsicBatch from web3 import Web3 from tools.constants import KP_GLOBAL_SUDO -from tools.peaq_eth_utils import sign_and_submit_evm_transaction +from tests.evm_utils import sign_and_submit_evm_transaction from peaq.utils import get_account_balance from tests import utils_func as TestUtils from tools.asset import wait_for_account_asset_change_wrap diff --git a/tests/bridge_xtokens_test.py b/tests/bridge_xtokens_test.py index ae970c8..535b9a3 100644 --- a/tests/bridge_xtokens_test.py +++ b/tests/bridge_xtokens_test.py @@ -8,7 +8,7 @@ from substrateinterface import SubstrateInterface, Keypair from tools.constants import ETH_URL, RELAYCHAIN_WS_URL from tools.constants import WS_URL, ACA_WS_URL, PARACHAIN_WS_URL -from tools.peaq_eth_utils import sign_and_submit_evm_transaction +from tests.evm_utils import sign_and_submit_evm_transaction from peaq.utils import get_account_balance from peaq.utils import ExtrinsicBatch from tools.asset import convert_enum_to_asset_id diff --git a/tests/conftest.py b/tests/conftest.py index 45c0286..3f2c354 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2,81 +2,11 @@ import importlib import sys import tools.utils # noqa: F401 +from tests.substrate_utils import monkey_submit_extrinsic_for_fee_weight +from tests.substrate_utils import generate_substrate_weight_fee_report +from tests.evm_utils import generate_evm_fee_report +from tests.evm_utils import sign_and_submit_evm_transaction from substrateinterface import SubstrateInterface -import os -import datetime -import json - -old_submit_extrinsic = SubstrateInterface.submit_extrinsic -all_weight_fee_data = {} - - -def compose_extrinsic_name(call): - module = call['call_module'] - function = call['call_function'] - extrinsic_name = f'{module}.{function}' - if module != 'Utility' and function != 'batch_all': - return extrinsic_name - - inner_names = [compose_extrinsic_name(call) for call in call['call_args'][0]['value']] - extrinsic_name += f'({", ".join(inner_names)})' - - return extrinsic_name - - -def get_transaction_fee(substrate, extrinsic_idx, block_hash): - events = substrate.get_events(block_hash) - for event in events: - if event.value['extrinsic_idx'] != extrinsic_idx: - continue - if event.value['module_id'] != 'TransactionPayment' or \ - event.value['event_id'] != 'TransactionFeePaid': - continue - return event['event'][1][1].value['actual_fee'] - event['event'][1][1].value['tip'] - - -def get_transaction_weight(substrate, extrinsic_idx, block_hash): - events = substrate.get_events(block_hash) - - for event in events: - if event.value['extrinsic_idx'] != extrinsic_idx: - continue - if event.value['module_id'] != 'System' or \ - event.value['phase'] != 'ApplyExtrinsic' or \ - 'dispatch_info' not in event.value['attributes']: - continue - # We didn't check event_id because it might be extrnisic error - return event.value['attributes']['dispatch_info']['weight']['ref_time'] - - -def monkey_submit_extrinsic_for_fee_weight(self, extrinsic, wait_for_inclusion, wait_for_finalization): - receipt = old_submit_extrinsic(self, extrinsic, wait_for_inclusion, wait_for_finalization) - - block_hash = receipt.block_hash - block = self.get_block(block_hash) - for extrinsic in block['extrinsics']: - if f'0x{extrinsic.extrinsic_hash.hex()}' != receipt.extrinsic_hash: - continue - name = compose_extrinsic_name(extrinsic.value['call']) - fee = get_transaction_fee(self, receipt.extrinsic_idx, block_hash) - weight = get_transaction_weight(self, receipt.extrinsic_idx, block_hash) - if name in all_weight_fee_data: - all_weight_fee_data[name].append({ - 'fee': fee, - 'weight': weight - }) - else: - all_weight_fee_data[name] = [{ - 'fee': fee, - 'weight': weight - }] - - break - else: - raise Exception('Extrinsic not found in the block') - - return receipt - SubstrateInterface.submit_extrinsic = monkey_submit_extrinsic_for_fee_weight @@ -90,32 +20,6 @@ def pytest_runtest_setup(item): importlib.reload(sys.modules['peaq.utils']) -def process_weight_fee_data(): - summary_data = {} - for extrinsic_name, data in all_weight_fee_data.items(): - summary_data[extrinsic_name] = { - 'fee': sum([d['fee'] for d in data]) / len(data), - 'weight': sum([d['weight'] for d in data]) / len(data), - 'len': len(data) - } - return summary_data - - def pytest_sessionfinish(session, exitstatus): - # get date by format "YYYY-MM-DD-HH-MM" - now = datetime.datetime.now() - date = now.strftime("%Y-%m-%d-%H-%M") - - folder = "reports" - report_file = f"weight_fee_summary_{date}.json" - - if not os.path.exists(folder): - os.makedirs(folder) - - report_path = os.path.join(folder, report_file) - summary_data = process_weight_fee_data() - with open(report_path, "w") as f: - json.dump(summary_data, f, indent=4) - - print('') - print(f"Weight/fee data saved to {report_path}") + generate_substrate_weight_fee_report() + generate_evm_fee_report() diff --git a/tests/evm_eth_rpc_test.py b/tests/evm_eth_rpc_test.py index c921274..a476429 100644 --- a/tests/evm_eth_rpc_test.py +++ b/tests/evm_eth_rpc_test.py @@ -7,7 +7,7 @@ from peaq.utils import ExtrinsicBatch from tools.constants import KP_GLOBAL_SUDO from tools.constants import WS_URL, ETH_URL -from tools.peaq_eth_utils import sign_and_submit_evm_transaction +from tests.evm_utils import sign_and_submit_evm_transaction from tools.peaq_eth_utils import get_eth_chain_id from tools.peaq_eth_utils import deploy_contract from tools.peaq_eth_utils import call_eth_transfer_a_lot diff --git a/tests/evm_utils.py b/tests/evm_utils.py new file mode 100644 index 0000000..399ed64 --- /dev/null +++ b/tests/evm_utils.py @@ -0,0 +1,113 @@ +from tools import peaq_eth_utils as PeaqEthUtils +from eth_utils import keccak +import os +import json +import datetime + +evm_all_fee_data = {} +func_selector_dict = {} + + +def load_all_function(folder, abi): + function_dict = {} + for item in abi: + if item["type"] != "function": + continue + function_signature = f"{item['name']}({','.join([input['type'] for input in item['inputs']])})" + selector = "0x" + keccak(text=function_signature).hex()[:8] + function_dict[selector] = f'{folder}.{item["name"]}' + return function_dict + + +def load_all_abi(): + # traverse ETH/*/abi files + folders = os.listdir("ETH") + for folder in folders: + if folder == ".pytest_cache": + continue + with open(os.path.join('ETH', folder, 'abi')) as f: + abi = json.load(f) + for k, v in load_all_function(folder, abi).items(): + func_selector_dict[k] = v + + +def compose_batch_function_name(w3, input_data): + batch_abi = json.load(open('ETH/batch/abi')) + + batch = w3.eth.contract(address='0x0000000000000000000000000000000000000805', abi=batch_abi) + func_name, args = batch.decode_function_input(input_data) + inner_func_names = [] + for call_data in args['callData']: + function_selector = "0x" + call_data.hex()[:8] + if function_selector not in func_selector_dict: + inner_func_names.append(f'Unknown.{function_selector}') + else: + inner_func_names.append(func_selector_dict[function_selector]) + return f"({','.join(inner_func_names)})" + + +def compose_function_name(w3, tx): + if 'data' not in tx: + return 'Transfer' + input_data = tx['data'] + function_selector = input_data[:10] + if function_selector not in func_selector_dict: + import pdb + pdb.set_trace() + return f'Unknown.{function_selector}' + + name = func_selector_dict[function_selector] + if name.startswith('batch'): + inner_name = compose_batch_function_name(w3, input_data) + name += inner_name + + return name + + +def sign_and_submit_evm_transaction(tx, w3, signer): + receipt = PeaqEthUtils.sign_and_submit_evm_transaction(tx, w3, signer) + gas_used = receipt['gasUsed'] + effective_gas_price = receipt['effectiveGasPrice'] + + if not func_selector_dict: + load_all_abi() + + name = compose_function_name(w3, tx) + fee = int(w3.from_wei(gas_used * effective_gas_price, 'wei')) + if name not in evm_all_fee_data: + evm_all_fee_data[name] = [] + evm_all_fee_data[name].append({ + 'fee': fee, + }) + + return receipt + + +def generate_evm_fee_report(): + # get date by format "YYYY-MM-DD-HH-MM" + now = datetime.datetime.now() + date = now.strftime("%Y-%m-%d-%H-%M") + + folder = "reports" + report_file = f"evm_fee_summary_{date}.json" + + if not os.path.exists(folder): + os.makedirs(folder) + + report_path = os.path.join(folder, report_file) + summary_data = process_fee_data() + with open(report_path, "w") as f: + json.dump(summary_data, f, indent=4) + + print('') + print(f"EVM fee data saved to {report_path}") + + +def process_fee_data(): + summary_data = {} + for extrinsic_name, data in evm_all_fee_data.items(): + summary_data[extrinsic_name] = { + 'fee': sum([d['fee'] for d in data]) / len(data), + 'len': len(data) + } + return summary_data diff --git a/tests/pallet_address_unification.py b/tests/pallet_address_unification.py index 434f7c2..41c1c66 100644 --- a/tests/pallet_address_unification.py +++ b/tests/pallet_address_unification.py @@ -3,7 +3,7 @@ from substrateinterface import SubstrateInterface, Keypair, KeypairType from tools.constants import WS_URL, ETH_URL from tools.constants import KP_GLOBAL_SUDO -from tools.peaq_eth_utils import sign_and_submit_evm_transaction +from tests.evm_utils import sign_and_submit_evm_transaction from tools.asset import batch_create_asset, batch_mint, get_valid_asset_id from tools.asset import get_asset_balance from tools.evm_claim_sign import calculate_claim_signature, claim_account diff --git a/tests/pallet_rbac_test.py b/tests/pallet_rbac_test.py index b31d4cc..08d25d6 100644 --- a/tests/pallet_rbac_test.py +++ b/tests/pallet_rbac_test.py @@ -459,6 +459,8 @@ def test_pallet_rbac(self): # Success tests, default test setup kp_src = KP_TEST fund(self.substrate, KP_GLOBAL_SUDO, KP_TEST, 1000 * 10 ** 18) + import pdb + pdb.set_trace() reserved_before = get_balance_reserve_value(self.substrate, kp_src.ss58_address, 'peaqrbac') self.rbac_rpc_setup(kp_src) diff --git a/tests/substrate_utils.py b/tests/substrate_utils.py new file mode 100644 index 0000000..e51681b --- /dev/null +++ b/tests/substrate_utils.py @@ -0,0 +1,110 @@ +import tools.utils # noqa: F401 +from substrateinterface import SubstrateInterface +import os +import datetime +import json +from tools.constants import PARACHAIN_WS_URL + +old_submit_extrinsic = SubstrateInterface.submit_extrinsic +substrate_all_weight_fee_data = {} + + +def compose_extrinsic_name(call): + module = call['call_module'] + function = call['call_function'] + extrinsic_name = f'{module}.{function}' + if module == 'Sudo' and function == 'sudo': + inner_call = call['call_args'][0]['value'] + composed_inner_name = compose_extrinsic_name(inner_call) + return f'[Sudo.sudo.{composed_inner_name}]' + if module != 'Utility' and function != 'batch_all': + return extrinsic_name + + inner_names = [compose_extrinsic_name(call) for call in call['call_args'][0]['value']] + extrinsic_name += f'({",".join(inner_names)})' + + return extrinsic_name + + +def get_transaction_fee(substrate, extrinsic_idx, block_hash): + events = substrate.get_events(block_hash) + for event in events: + if event.value['extrinsic_idx'] != extrinsic_idx: + continue + if event.value['module_id'] != 'TransactionPayment' or \ + event.value['event_id'] != 'TransactionFeePaid': + continue + return event['event'][1][1].value['actual_fee'] - event['event'][1][1].value['tip'] + + +def get_transaction_weight(substrate, extrinsic_idx, block_hash): + events = substrate.get_events(block_hash) + + for event in events: + if event.value['extrinsic_idx'] != extrinsic_idx: + continue + if event.value['module_id'] != 'System' or \ + event.value['phase'] != 'ApplyExtrinsic' or \ + 'dispatch_info' not in event.value['attributes']: + continue + # We didn't check event_id because it might be extrnisic error + return event.value['attributes']['dispatch_info']['weight']['ref_time'] + + +def monkey_submit_extrinsic_for_fee_weight(self, extrinsic, wait_for_inclusion, wait_for_finalization): + receipt = old_submit_extrinsic(self, extrinsic, wait_for_inclusion, wait_for_finalization) + if self.url != PARACHAIN_WS_URL: + return receipt + + block_hash = receipt.block_hash + block = self.get_block(block_hash) + for extrinsic in block['extrinsics']: + if f'0x{extrinsic.extrinsic_hash.hex()}' != receipt.extrinsic_hash: + continue + name = compose_extrinsic_name(extrinsic.value['call']) + fee = get_transaction_fee(self, receipt.extrinsic_idx, block_hash) + weight = get_transaction_weight(self, receipt.extrinsic_idx, block_hash) + if name not in substrate_all_weight_fee_data: + substrate_all_weight_fee_data[name] = [] + + substrate_all_weight_fee_data[name].append({ + 'fee': fee, + 'weight': weight + }) + + break + else: + raise Exception('Extrinsic not found in the block') + + return receipt + + +def generate_substrate_weight_fee_report(): + # get date by format "YYYY-MM-DD-HH-MM" + now = datetime.datetime.now() + date = now.strftime("%Y-%m-%d-%H-%M") + + folder = "reports" + report_file = f"substrate_weight_fee_summary_{date}.json" + + if not os.path.exists(folder): + os.makedirs(folder) + + report_path = os.path.join(folder, report_file) + summary_data = process_weight_fee_data() + with open(report_path, "w") as f: + json.dump(summary_data, f, indent=4) + + print('') + print(f"Weight/fee data saved to {report_path}") + + +def process_weight_fee_data(): + summary_data = {} + for extrinsic_name, data in substrate_all_weight_fee_data.items(): + summary_data[extrinsic_name] = { + 'fee': sum([d['fee'] for d in data]) / len(data), + 'weight': sum([d['weight'] for d in data]) / len(data), + 'len': len(data) + } + return summary_data diff --git a/tools/uniswap_test_setup.py b/tools/uniswap_test_setup.py index f8a227f..d9236a6 100644 --- a/tools/uniswap_test_setup.py +++ b/tools/uniswap_test_setup.py @@ -15,7 +15,7 @@ ASSET_FACTORY_ABI_FILE = 'ETH/asset-factory/abi' ASSET_FACTORY_ADDR = '0x0000000000000000000000000000000000000806' -IERC20PLUS_ABI_FILE = 'ETH/erc20/plus.abi' +IERC20PLUS_ABI_FILE = 'ETH/erc20plus/abi' OWNER_PRIVATE_KEY = '0x77007083faa6e64d0884009165627d0067f845c13736eff45e624702f797eff3' SINGER_PRIVATE_KEY = '0x1b54e09b085972864787fcf08d06e2b16159a64655afb85cb8864f459125999e' From a8e61adb3f908b16fd1e44c43845cb9dd0bbbdcd Mon Sep 17 00:00:00 2001 From: jaypan Date: Mon, 16 Dec 2024 05:11:02 +0100 Subject: [PATCH 3/6] Modification --- tests/evm_utils.py | 9 ++++--- tests/pallet_rbac_test.py | 2 -- tools/peaq_eth_utils.py | 56 +++++++++++++++++++++++++++------------ 3 files changed, 45 insertions(+), 22 deletions(-) diff --git a/tests/evm_utils.py b/tests/evm_utils.py index 399ed64..68ddd10 100644 --- a/tests/evm_utils.py +++ b/tests/evm_utils.py @@ -1,8 +1,9 @@ from tools import peaq_eth_utils as PeaqEthUtils -from eth_utils import keccak +from eth_utils import function_abi_to_4byte_selector import os import json import datetime +from tools.constants import PARACHAIN_ETH_URL evm_all_fee_data = {} func_selector_dict = {} @@ -13,8 +14,7 @@ def load_all_function(folder, abi): for item in abi: if item["type"] != "function": continue - function_signature = f"{item['name']}({','.join([input['type'] for input in item['inputs']])})" - selector = "0x" + keccak(text=function_signature).hex()[:8] + selector = f'0x{function_abi_to_4byte_selector(item).hex()}' function_dict[selector] = f'{folder}.{item["name"]}' return function_dict @@ -69,6 +69,9 @@ def sign_and_submit_evm_transaction(tx, w3, signer): gas_used = receipt['gasUsed'] effective_gas_price = receipt['effectiveGasPrice'] + if w3.provider.endpoint_uri != PARACHAIN_ETH_URL: + return receipt + if not func_selector_dict: load_all_abi() diff --git a/tests/pallet_rbac_test.py b/tests/pallet_rbac_test.py index 08d25d6..b31d4cc 100644 --- a/tests/pallet_rbac_test.py +++ b/tests/pallet_rbac_test.py @@ -459,8 +459,6 @@ def test_pallet_rbac(self): # Success tests, default test setup kp_src = KP_TEST fund(self.substrate, KP_GLOBAL_SUDO, KP_TEST, 1000 * 10 ** 18) - import pdb - pdb.set_trace() reserved_before = get_balance_reserve_value(self.substrate, kp_src.ss58_address, 'peaqrbac') self.rbac_rpc_setup(kp_src) diff --git a/tools/peaq_eth_utils.py b/tools/peaq_eth_utils.py index cb23988..50ad722 100644 --- a/tools/peaq_eth_utils.py +++ b/tools/peaq_eth_utils.py @@ -109,25 +109,47 @@ def calculate_evm_default_addr(sub_addr): return Web3.to_checksum_address(new_addr.lower()) -def sign_and_submit_evm_transaction(tx, w3, signer): - signed_txn = w3.eth.account.sign_transaction(tx, private_key=signer.private_key) - for i in range(3): +def send_raw_tx(w3, signed_txn): + try: tx_hash = w3.eth.send_raw_transaction(signed_txn.rawTransaction) print(f'evm tx: {tx_hash.hex()}') - try: - receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=ETH_TIMEOUT) - except Web3Exceptions.TimeExceeded: - print(f'Timeout for tx: {tx_hash.hex()}') - continue - # Check whether the block is finalized or not. If not, wait for it + return tx_hash + except ValueError as e: + print("Error:", e) + if "already known" in str(e): + print("Transaction already known by the node.") + return signed_txn.hash + else: + raise e + + +def wait_w3_tx(w3, tx_hash, timimeout=ETH_TIMEOUT): + try: + receipt = w3.eth.wait_for_transaction_receipt(tx_hash, timeout=ETH_TIMEOUT) while w3.eth.get_block('finalized').number < receipt.blockNumber: time.sleep(BLOCK_GENERATE_TIME) - try: - receipt = w3.eth.get_transaction_receipt(tx_hash) - # Check the transaction is existed or not, if not, go back to send again - print(f'evm receipt: {receipt.blockNumber}-{receipt.transactionIndex}') - return receipt - except Web3Exceptions.TransactionNotFound: - print(f'Tx {tx_hash.hex()} is not found') - continue + except Web3Exceptions.TimeExhausted: + print(f'Timeout for tx: {tx_hash.hex()}') + except Exception as e: + raise e + + +def sign_and_submit_evm_transaction(tx, w3, signer): + signed_txn = w3.eth.account.sign_transaction(tx, private_key=signer.private_key) + for i in range(3): + tx_hash = send_raw_tx(w3, signed_txn) + wait_w3_tx(w3, tx_hash) + + # Check whether the block is finalized or not. If not, wait for it + for i in range(3): + try: + receipt = w3.eth.get_transaction_receipt(tx_hash) + # Check the transaction is existed or not, if not, go back to send again + print(f'evm receipt: {receipt.blockNumber}-{receipt.transactionIndex}') + return receipt + except Web3Exceptions.TransactionNotFound: + print(f'Tx {tx_hash.hex()} is not found') + time.sleep(BLOCK_GENERATE_TIME * 2) + else: + print(f'Cannot find tx {tx_hash.hex()}') raise IOError('Cannot send transaction') From c454e512aa8eebaa963adb68c30ae37f94341f05 Mon Sep 17 00:00:00 2001 From: jaypan Date: Mon, 16 Dec 2024 05:25:06 +0100 Subject: [PATCH 4/6] Fix flake8 --- tests/conftest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 3f2c354..e587f6b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -5,7 +5,6 @@ from tests.substrate_utils import monkey_submit_extrinsic_for_fee_weight from tests.substrate_utils import generate_substrate_weight_fee_report from tests.evm_utils import generate_evm_fee_report -from tests.evm_utils import sign_and_submit_evm_transaction from substrateinterface import SubstrateInterface SubstrateInterface.submit_extrinsic = monkey_submit_extrinsic_for_fee_weight From 0482c0db9fc4df03637221528b3374c10278e892 Mon Sep 17 00:00:00 2001 From: jaypan Date: Mon, 16 Dec 2024 05:47:22 +0100 Subject: [PATCH 5/6] Add tips --- tests/substrate_utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/substrate_utils.py b/tests/substrate_utils.py index e51681b..22fbffa 100644 --- a/tests/substrate_utils.py +++ b/tests/substrate_utils.py @@ -34,7 +34,7 @@ def get_transaction_fee(substrate, extrinsic_idx, block_hash): if event.value['module_id'] != 'TransactionPayment' or \ event.value['event_id'] != 'TransactionFeePaid': continue - return event['event'][1][1].value['actual_fee'] - event['event'][1][1].value['tip'] + return event['event'][1][1].value['actual_fee'], event['event'][1][1].value['tip'] def get_transaction_weight(substrate, extrinsic_idx, block_hash): @@ -62,13 +62,14 @@ def monkey_submit_extrinsic_for_fee_weight(self, extrinsic, wait_for_inclusion, if f'0x{extrinsic.extrinsic_hash.hex()}' != receipt.extrinsic_hash: continue name = compose_extrinsic_name(extrinsic.value['call']) - fee = get_transaction_fee(self, receipt.extrinsic_idx, block_hash) + fee, tip = get_transaction_fee(self, receipt.extrinsic_idx, block_hash) weight = get_transaction_weight(self, receipt.extrinsic_idx, block_hash) if name not in substrate_all_weight_fee_data: substrate_all_weight_fee_data[name] = [] substrate_all_weight_fee_data[name].append({ 'fee': fee, + 'tip': tip, 'weight': weight }) @@ -105,6 +106,7 @@ def process_weight_fee_data(): summary_data[extrinsic_name] = { 'fee': sum([d['fee'] for d in data]) / len(data), 'weight': sum([d['weight'] for d in data]) / len(data), + 'tip': sum([d['tip'] for d in data]) / len(data), 'len': len(data) } return summary_data From 9f84c644f97341ec9b3f106629354baeffb97bb3 Mon Sep 17 00:00:00 2001 From: jaypan Date: Tue, 17 Dec 2024 07:39:08 +0100 Subject: [PATCH 6/6] Add the example code for the fetch timestamp --- tools/example/fetch_timestamp.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 tools/example/fetch_timestamp.py diff --git a/tools/example/fetch_timestamp.py b/tools/example/fetch_timestamp.py new file mode 100644 index 0000000..c626fe2 --- /dev/null +++ b/tools/example/fetch_timestamp.py @@ -0,0 +1,23 @@ +import sys +sys.path.append('./') + +from substrateinterface import SubstrateInterface +import datetime + + +if __name__ == '__main__': + URL = 'ws://127.0.0.1:10044' + substrate = SubstrateInterface(url=URL) + + now_block_num = substrate.get_block_number(None) + block_hash = substrate.get_block_hash(now_block_num) + block_info = substrate.get_block(block_hash, include_author=True) + for extrinsic in block_info['extrinsics']: + if extrinsic.value['call']['call_module'] != 'Timestamp': + continue + if extrinsic.value['call']['call_function'] != 'set': + continue + timestamp = extrinsic.value['call']['call_args'][0]['value'] + # Convert time stamp to human readable format + human_readable_time = datetime.datetime.fromtimestamp(timestamp / 1000).strftime('%Y-%m-%d %H:%M:%S') + print(human_readable_time)