diff --git a/requirements.txt b/requirements.txt index 55755fe..c4b7114 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,6 @@ eventlet==0.33.3 requests==2.31.0 -scalecodec==1.2.7 -substrate_interface==1.7.4 +substrate_interface==1.7.11 behave==1.2.6 peaq-py==0.2.2 eth-account==0.9.0 @@ -9,3 +8,4 @@ web3==6.11.2 pytest==7.4.3 python-on-whales==0.66.0 eth-typing==3.5.2 +eth-utils==2.3.1 diff --git a/tests/bridge_balance_erc20_test.py b/tests/bridge_balance_erc20_test.py index 242d233..55727da 100644 --- a/tests/bridge_balance_erc20_test.py +++ b/tests/bridge_balance_erc20_test.py @@ -121,6 +121,10 @@ def test_balance_erc20_total_issuance(self): f'Error: {data} != {total_balance.value}') def test_balance_erc20_balance_of(self): + batch = ExtrinsicBatch(self._substrate, KP_GLOBAL_SUDO) + batch_fund(batch, self._eth_kp_src['substrate'], 100 * 10 ** 18) + receipt = batch.execute() + self.assertTrue(receipt.is_success) contract = get_contract(self._w3, BALANCE_ERC20_ADDR, BALANCE_ERC20_ABI_FILE) evm_balance = contract.functions.balanceOf(self._eth_kp_src['eth']).call() sub_balance = get_account_balance(self._substrate, self._eth_kp_src['substrate']) diff --git a/tests/bridge_multiple_collator_test.py b/tests/bridge_multiple_collator_test.py new file mode 100644 index 0000000..b3b7017 --- /dev/null +++ b/tests/bridge_multiple_collator_test.py @@ -0,0 +1,196 @@ +import pytest +import unittest +from tests.utils_func import restart_parachain_and_runtime_upgrade +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 peaq.utils import ExtrinsicBatch +from tools.peaq_eth_utils import get_contract +from tools.peaq_eth_utils import get_eth_chain_id +from tools.peaq_eth_utils import get_eth_info +from tools.constants import KP_GLOBAL_SUDO, KP_COLLATOR +from peaq.utils import get_block_hash +from web3 import Web3 + + +PARACHAIN_STAKING_ABI_FILE = 'ETH/parachain-staking/abi' +PARACHAIN_STAKING_ADDR = '0x0000000000000000000000000000000000000807' + + +# [TODO] Should refine the functions +@pytest.mark.relaunch +@pytest.mark.eth +class bridge_parachain_staking_collators_test(unittest.TestCase): + + @classmethod + def setUpClass(cls): + restart_parachain_and_runtime_upgrade() + wait_until_block_height(SubstrateInterface(url=RELAYCHAIN_WS_URL), 1) + wait_until_block_height(SubstrateInterface(url=WS_URL), 1) + + def setUp(self): + wait_until_block_height(SubstrateInterface(url=WS_URL), 1) + + self._substrate = SubstrateInterface(url=WS_URL) + self._w3 = Web3(Web3.HTTPProvider(ETH_URL)) + self._kp_moon = get_eth_info() + self._kp_mars = get_eth_info() + self._eth_chain_id = get_eth_chain_id(self._substrate) + self._kp_src = Keypair.create_from_uri('//Moon') + + def _fund_users(self, num=100 * 10 ** 18): + if num < 100 * 10 ** 18: + num = 100 * 10 ** 18 + # Fund users + batch = ExtrinsicBatch(self._substrate, KP_GLOBAL_SUDO) + batch.compose_sudo_call( + 'Balances', + 'force_set_balance', + { + 'who': self._kp_moon['substrate'], + 'new_free': num, + } + ) + batch.compose_sudo_call( + 'Balances', + 'force_set_balance', + { + 'who': self._kp_mars['substrate'], + 'new_free': num, + } + ) + batch.compose_sudo_call( + 'Balances', + 'force_set_balance', + { + 'who': self._kp_src.ss58_address, + 'new_free': num, + } + ) + return batch.execute() + + def evm_join_delegators(self, contract, eth_kp_src, sub_collator_addr, stake): + w3 = self._w3 + nonce = w3.eth.get_transaction_count(eth_kp_src.ss58_address) + tx = contract.functions.joinDelegators(sub_collator_addr, stake).build_transaction({ + 'from': eth_kp_src.ss58_address, + 'nonce': nonce, + 'chainId': self._eth_chain_id}) + + return sign_and_submit_evm_transaction(tx, w3, eth_kp_src) + + def evm_delegate_another_candidate(self, contract, eth_kp_src, sub_collator_addr, stake): + w3 = self._w3 + nonce = w3.eth.get_transaction_count(eth_kp_src.ss58_address) + tx = contract.functions.delegateAnotherCandidate(sub_collator_addr, stake).build_transaction({ + 'from': eth_kp_src.ss58_address, + 'nonce': nonce, + 'chainId': self._eth_chain_id}) + + return sign_and_submit_evm_transaction(tx, w3, eth_kp_src) + + def evm_delegator_leave_delegators(self, contract, eth_kp_src): + w3 = self._w3 + nonce = w3.eth.get_transaction_count(eth_kp_src.ss58_address) + tx = contract.functions.leaveDelegators().build_transaction({ + 'from': eth_kp_src.ss58_address, + 'nonce': nonce, + 'chainId': self._eth_chain_id}) + + return sign_and_submit_evm_transaction(tx, w3, eth_kp_src) + + def evm_delegator_revoke_delegation(self, contract, eth_kp_src, sub_collator_addr): + w3 = self._w3 + nonce = w3.eth.get_transaction_count(eth_kp_src.ss58_address) + tx = contract.functions.revokeDelegation(sub_collator_addr).build_transaction({ + 'from': eth_kp_src.ss58_address, + 'nonce': nonce, + 'chainId': self._eth_chain_id}) + + return sign_and_submit_evm_transaction(tx, w3, eth_kp_src) + + def evm_delegator_unlock_unstaked(self, contract, eth_kp_src, eth_addr): + w3 = self._w3 + nonce = w3.eth.get_transaction_count(eth_kp_src.ss58_address) + tx = contract.functions.unlockUnstaked(eth_addr).build_transaction({ + 'from': eth_kp_src.ss58_address, + 'nonce': nonce, + 'chainId': self._eth_chain_id}) + + return sign_and_submit_evm_transaction(tx, w3, eth_kp_src) + + def get_stake_number(self, sub_addr): + data = self._substrate.query('ParachainStaking', 'DelegatorState', [sub_addr]) + # {'delegations': + # [{'owner': '5CiPPseXPECbkjWCa6MnjNokrgYjMqmKndv2rSnekmSK2DjL', 'amount': 262144000}], + # 'total': 262144000} + return data.value + + def get_event(self, block_hash, module, event): + events = self._substrate.get_events(block_hash) + for e in events: + if e.value['event']['module_id'] == module and e.value['event']['event_id'] == event: + return {'attributes': e.value['event']['attributes']} + return None + + def test_delegator_another_candidate(self): + contract = get_contract(self._w3, PARACHAIN_STAKING_ADDR, PARACHAIN_STAKING_ABI_FILE) + out = contract.functions.getCollatorList().call() + collator_num = out[0][1] + collator_eth_addr = out[0][0] + + # Fund users + receipt = self._fund_users(collator_num * 3) + self.assertEqual(receipt.is_success, True, f'fund_users fails, receipt: {receipt}') + + evm_receipt = self.evm_join_delegators(contract, self._kp_moon['kp'], collator_eth_addr, collator_num) + self.assertEqual(evm_receipt['status'], 1, f'join fails, evm_receipt: {evm_receipt}') + bl_hash = get_block_hash(self._substrate, evm_receipt['blockNumber']) + event = self.get_event(bl_hash, 'ParachainStaking', 'Delegation') + self.assertEqual( + event['attributes'], + (self._kp_moon['substrate'], collator_num, KP_COLLATOR.ss58_address, 2 * collator_num), + f'join fails, event: {event}') + + # Check the delegator's stake + stake = self.get_stake_number(self._kp_moon['substrate']) + self.assertEqual(stake['total'], collator_num, f'join fails, stake: {stake}, collator_num: {collator_num}') + + batch = ExtrinsicBatch(self._substrate, self._kp_src) + batch.compose_call( + 'ParachainStaking', + 'join_candidates', + { + 'stake': collator_num + } + ) + receipt = batch.execute() + self.assertEqual(receipt.is_success, True, f'joinCandidate fails, receipt: {receipt}') + + # Force session * 2 + batch = ExtrinsicBatch(self._substrate, KP_GLOBAL_SUDO) + batch.compose_sudo_call( + 'ParachainStaking', + 'force_new_round', + {} + ) + receipt = batch.execute() + self.assertEqual(receipt.is_success, True, f'force_new_round fails, receipt: {receipt}') + receipt = batch.execute() + self.assertEqual(receipt.is_success, True, f'force_new_round fails, receipt: {receipt}') + + out = contract.functions.getCollatorList().call() + collator_eth_addr = out[0][0] + collator_eth_addr = [out[i][0] for i in range(len(out)) if out[i][0] != collator_eth_addr][0] + + evm_receipt = self.evm_delegate_another_candidate(contract, self._kp_moon['kp'], collator_eth_addr, collator_num) + self.assertEqual(evm_receipt['status'], 1, f'join fails, evm_receipt: {evm_receipt}') + stake = self.get_stake_number(self._kp_moon['substrate']) + self.assertEqual(stake['total'], collator_num * 2, f'join fails, stake: {stake}, collator_num: {collator_num * 2}') + + # Force leave all + evm_receipt = self.evm_delegator_leave_delegators(contract, self._kp_moon['kp']) + self.assertEqual(evm_receipt['status'], 1, f'leave fails, evm_receipt: {evm_receipt}') + stake = self.get_stake_number(self._kp_moon['substrate']) + self.assertEqual(stake, None) diff --git a/tests/bridge_parachain_staking_test.py b/tests/bridge_parachain_staking_test.py index bd6ee50..a0d6a8c 100644 --- a/tests/bridge_parachain_staking_test.py +++ b/tests/bridge_parachain_staking_test.py @@ -2,8 +2,8 @@ import unittest from tests.utils_func import restart_parachain_and_runtime_upgrade from tools.runtime_upgrade import wait_until_block_height -from substrateinterface import SubstrateInterface -from tools.constants import WS_URL, ETH_URL +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 peaq.utils import ExtrinsicBatch from tools.peaq_eth_utils import get_contract @@ -27,6 +27,7 @@ class bridge_parachain_staking_test(unittest.TestCase): @classmethod def setUpClass(cls): restart_parachain_and_runtime_upgrade() + wait_until_block_height(SubstrateInterface(url=RELAYCHAIN_WS_URL), 1) wait_until_block_height(SubstrateInterface(url=WS_URL), 1) def setUp(self): @@ -37,6 +38,7 @@ def setUp(self): self._kp_moon = get_eth_info() self._kp_mars = get_eth_info() self._eth_chain_id = get_eth_chain_id(self._substrate) + self._kp_src = Keypair.create_from_uri('//Moon') def _fund_users(self, num=100 * 10 ** 18): if num < 100 * 10 ** 18: @@ -59,6 +61,14 @@ def _fund_users(self, num=100 * 10 ** 18): 'new_free': num, } ) + batch.compose_sudo_call( + 'Balances', + 'force_set_balance', + { + 'who': self._kp_src.ss58_address, + 'new_free': num, + } + ) return batch.execute() def evm_join_delegators(self, contract, eth_kp_src, sub_collator_addr, stake): diff --git a/tests/bridge_xcmutils_test.py b/tests/bridge_xcmutils_test.py index 5c5f9bf..470b1eb 100644 --- a/tests/bridge_xcmutils_test.py +++ b/tests/bridge_xcmutils_test.py @@ -1,6 +1,6 @@ import unittest from tests.utils_func import restart_parachain_and_runtime_upgrade -from tools.constants import WS_URL, ETH_URL, ACA_WS_URL +from tools.constants import WS_URL, ETH_URL, ACA_WS_URL, RELAYCHAIN_WS_URL from tools.constants import ACA_PD_CHAIN_ID from tools.runtime_upgrade import wait_until_block_height from tools.peaq_eth_utils import get_contract @@ -33,6 +33,7 @@ class TestBridgeXCMUtils(unittest.TestCase): @classmethod def setUpClass(cls): restart_parachain_and_runtime_upgrade() + wait_until_block_height(SubstrateInterface(url=RELAYCHAIN_WS_URL), 1) wait_until_block_height(SubstrateInterface(url=WS_URL), 1) wait_until_block_height(SubstrateInterface(url=ACA_WS_URL), 1) diff --git a/tests/evm_eth_rpc_test.py b/tests/evm_eth_rpc_test.py index f0f44a8..fe4c769 100644 --- a/tests/evm_eth_rpc_test.py +++ b/tests/evm_eth_rpc_test.py @@ -1,6 +1,7 @@ import pytest from substrateinterface import SubstrateInterface, Keypair, KeypairType +from tools.runtime_upgrade import wait_until_block_height from peaq.eth import calculate_evm_account, calculate_evm_addr from peaq.extrinsic import transfer from peaq.utils import ExtrinsicBatch @@ -12,6 +13,7 @@ from tools.peaq_eth_utils import call_eth_transfer_a_lot from tools.peaq_eth_utils import get_eth_balance, get_contract from tools.peaq_eth_utils import TX_SUCCESS_STATUS +from peaq.utils import get_account_balance from tests import utils_func as TestUtils from tools.peaq_eth_utils import get_eth_info from tools.utils import batch_fund @@ -24,7 +26,7 @@ ERC_TOKEN_TRANSFER = 34 HEX_STR = '1111' GAS_LIMIT = 4294967 -TOKEN_NUM = 10000 * pow(10, 15) +TOKEN_NUM = 10 * 10 ** 18 ABI_FILE = 'ETH/identity/abi' TOKEN_NUM_BASE = pow(10, 18) @@ -73,6 +75,7 @@ def call_copy(w3, address, kp_src, eth_chain_id, file_name, data): @pytest.mark.eth class TestEVMEthRPC(unittest.TestCase): def setUp(self): + wait_until_block_height(SubstrateInterface(url=WS_URL), 3) self._conn = SubstrateInterface(url=WS_URL) self._eth_chain_id = get_eth_chain_id(self._conn) self._kp_src = Keypair.create_from_uri('//Alice') @@ -82,6 +85,19 @@ def setUp(self): self._w3 = Web3(Web3.HTTPProvider(ETH_URL)) self._eth_deposited_src = calculate_evm_account(self._eth_src) + def test_evm_api_balance_same(self): + self._kp_moon = get_eth_info() + self._kp_mars = get_eth_info() + + batch = ExtrinsicBatch(self._conn, KP_GLOBAL_SUDO) + batch_fund(batch, self._kp_moon['substrate'], int(1.05 * 10 ** 18)) + receipt = batch.execute() + self.assertTrue(receipt.is_success) + + sub_balance = get_account_balance(self._conn, self._kp_moon['substrate']) + eth_balance = self._w3.eth.get_balance(self._kp_moon['kp'].ss58_address) + self.assertEqual(sub_balance, eth_balance, f"sub: {sub_balance} != eth: {eth_balance}") + @pytest.mark.skipif(TestUtils.is_not_peaq_chain() is True, reason='Only peaq chain evm tx change') def test_evm_fee(self): self._kp_moon = get_eth_info() @@ -93,13 +109,14 @@ def test_evm_fee(self): prev_balance = self._w3.eth.get_balance(self._kp_moon['kp'].ss58_address) nonce = self._w3.eth.get_transaction_count(self._kp_moon['kp'].ss58_address) + # gas/maxFeePerGas/maxPriorityFeePerGas is decided by metamask's value tx = { 'from': self._kp_moon['kp'].ss58_address, 'to': self._kp_mars['kp'].ss58_address, 'value': 1 * 10 ** 18, 'gas': 21000, - 'maxFeePerGas': 100 * 10 ** 9, - 'maxPriorityFeePerGas': 1 * 10 ** 9, + 'maxFeePerGas': 1000 * 10 ** 9, + 'maxPriorityFeePerGas': 1000 * 10 ** 9, 'nonce': nonce, 'chainId': self._eth_chain_id } diff --git a/tests/existential_deposits_test.py b/tests/existential_deposits_test.py index ad9c6b8..2d80d60 100644 --- a/tests/existential_deposits_test.py +++ b/tests/existential_deposits_test.py @@ -3,10 +3,10 @@ from substrateinterface import SubstrateInterface, Keypair from tools.utils import get_existential_deposit from tools.constants import WS_URL -from peaq.extrinsic import transfer @pytest.mark.substrate +@pytest.mark.skip(reason="Only test for the charging simulator") class TestExitentialDeposits(unittest.TestCase): def get_existential_deposit(self): return get_existential_deposit(self.substrate) @@ -18,17 +18,4 @@ def setUp(self): def test_local_token(self): token = self.get_existential_deposit() - self.assertGreater(token, 2) - token /= 2 - - # Execute -> Send local token to another account but below the Exitential Deposits - receipt = transfer( - self.substrate, - self.alice, - self.kp.ss58_address, - token, - 1 - ) - - # Check: the error happens - self.assertFalse(receipt.is_success) + self.assertEqual(token, 0) diff --git a/tests/metadata_test.py b/tests/metadata_test.py new file mode 100644 index 0000000..9ee574d --- /dev/null +++ b/tests/metadata_test.py @@ -0,0 +1,16 @@ +import unittest +import pytest + +from substrateinterface import SubstrateInterface +from tools.constants import WS_URL + + +@pytest.mark.substrate +class TestMetadata(unittest.TestCase): + def setUp(self): + self.substrate = SubstrateInterface(url=WS_URL) + + def test_check_meta(self): + metadata = self.substrate.get_block_metadata() + self.assertTrue('frame_metadata_hash_extension' in str(metadata.value)) + self.assertTrue('CheckMetadataHash' in str(metadata.value)) diff --git a/tests/pallet_did_test.py b/tests/pallet_did_test.py index 0f58791..f59a818 100644 --- a/tests/pallet_did_test.py +++ b/tests/pallet_did_test.py @@ -7,6 +7,17 @@ from peaq.utils import ExtrinsicBatch from peaq.did import did_add_payload, did_update_payload, did_remove_payload, did_rpc_read from tools.peaq_eth_utils import generate_random_hex +from tools.runtime_upgrade import wait_until_block_height +from tools.constants import PARACHAIN_WS_URL, RELAYCHAIN_WS_URL +from peaq.utils import get_chain +from tools.utils import get_modified_chain_spec + + +DID_MIN_DEPOSIT = { + 'peaq-dev': 0.1 * 10 ** 18, + 'krest-network': 0.1 * 10 ** 18, + 'peaq-network': 0.005 * 10 ** 18, +} @pytest.mark.substrate @@ -14,6 +25,10 @@ class TestPalletDid(unittest.TestCase): def setUp(self): self.substrate = SubstrateInterface(url=WS_URL) self.kp_src = Keypair.create_from_uri('//Alice') + wait_until_block_height(SubstrateInterface(url=RELAYCHAIN_WS_URL), 1) + wait_until_block_height(SubstrateInterface(url=PARACHAIN_WS_URL), 1) + self.chain_spec = get_chain(self.substrate) + self.chain_spec = get_modified_chain_spec(self.chain_spec) def test_did_add(self): key = generate_random_hex(20) @@ -31,6 +46,7 @@ def test_did_add(self): self.assertEqual(data['value'], value) reserved_after = get_balance_reserve_value(self.substrate, self.kp_src.ss58_address, 'peaq_did') self.assertGreater(reserved_after, reserved_before) + self.assertGreaterEqual(reserved_after - reserved_before, DID_MIN_DEPOSIT[self.chain_spec]) def test_did_update(self): key = generate_random_hex(20) diff --git a/tests/pallet_inflation_manager_test.py b/tests/pallet_inflation_manager_test.py index 85d9641..0fd587b 100644 --- a/tests/pallet_inflation_manager_test.py +++ b/tests/pallet_inflation_manager_test.py @@ -70,7 +70,7 @@ 'peaq-network': 5256000, 'peaq-network-fork': 5684095, 'peaq-dev': 5256000, - 'peaq-dev-fork': 5084632, + 'peaq-dev-fork': 10169264, # Because of the delay TGE 'krest-network': 3469624, 'krest-network-fork': 3469624, diff --git a/tests/pallet_rbac_test.py b/tests/pallet_rbac_test.py index 30417b9..b31d4cc 100644 --- a/tests/pallet_rbac_test.py +++ b/tests/pallet_rbac_test.py @@ -19,9 +19,20 @@ from peaq.rbac import rbac_rpc_fetch_groups from tools.utils import get_balance_reserve_value from tools.constants import KP_GLOBAL_SUDO +from tools.runtime_upgrade import wait_until_block_height +from tools.constants import PARACHAIN_WS_URL, RELAYCHAIN_WS_URL +from peaq.utils import get_chain +from tools.utils import get_modified_chain_spec import unittest +RBAC_MIN_DEPOSIT = { + 'peaq-dev': 0.1 * 10 ** 18, + 'krest-network': 0.1 * 10 ** 18, + 'peaq-network': 0.005 * 10 ** 18, +} + + KP_TEST = Keypair.create_from_mnemonic(Keypair.generate_mnemonic()) RANDOM_PREFIX = KP_TEST.public_key.hex()[2:26] @@ -437,6 +448,10 @@ def verify_rpc_fail_disabled_id(self, kp_src): def setUp(self): self.substrate = SubstrateInterface(url=WS_URL) + wait_until_block_height(SubstrateInterface(url=RELAYCHAIN_WS_URL), 1) + wait_until_block_height(SubstrateInterface(url=PARACHAIN_WS_URL), 1) + self.chain_spec = get_chain(self.substrate) + self.chain_spec = get_modified_chain_spec(self.chain_spec) def test_pallet_rbac(self): print('---- pallet_rbac_test!! ----') @@ -475,6 +490,21 @@ def test_pallet_rbac(self): print(f'🔥 Test/{func}, Failed') raise + def test_reserve_check(self): + ROLE_TEST = '{0}03456789abcdef0123456111abcde00123456111'.format(RANDOM_PREFIX) + + kp_src = Keypair.create_from_mnemonic(Keypair.generate_mnemonic()) + fund(self.substrate, KP_GLOBAL_SUDO, kp_src, 1000 * 10 ** 18) + + reserved_before = get_balance_reserve_value(self.substrate, kp_src.ss58_address, 'peaqrbac') + batch = ExtrinsicBatch(self.substrate, kp_src) + rbac_add_role_payload(batch, f'0x{ROLE_TEST}', ROLE_NM1) + receipt = batch.execute() + self.assertTrue(receipt.is_success, f'Extrinsic-call-stack failed: {receipt.error_message}') + reserved_after = get_balance_reserve_value(self.substrate, kp_src.ss58_address, 'peaqrbac') + self.assertGreater(reserved_after, reserved_before) + self.assertGreaterEqual(reserved_after - reserved_before, RBAC_MIN_DEPOSIT[self.chain_spec]) + def test_delete_types(self): ROLE_TEST = '{0}03456789abcdef0123456111abcdef0123456111'.format(RANDOM_PREFIX) GROUP_TEST = '{0}0defabcdabcdefabc111abcdabcdefabcdefa111'.format(RANDOM_PREFIX) diff --git a/tests/pallet_storage_test.py b/tests/pallet_storage_test.py index fa497f8..27b34e1 100644 --- a/tests/pallet_storage_test.py +++ b/tests/pallet_storage_test.py @@ -5,10 +5,21 @@ from substrateinterface import SubstrateInterface, Keypair from peaq.utils import ExtrinsicBatch from peaq.storage import storage_add_payload, storage_update_payload, storage_rpc_read +from tools.utils import get_modified_chain_spec +from tools.runtime_upgrade import wait_until_block_height +from tools.constants import PARACHAIN_WS_URL, RELAYCHAIN_WS_URL +from peaq.utils import get_chain import unittest +STORAGE_MIN_DEPOSIT = { + 'peaq-dev': 0.1 * 10 ** 18, + 'krest-network': 0.1 * 10 ** 18, + 'peaq-network': 0.005 * 10 ** 18, +} + + def storage_remove_payload(batch, item_type): batch.compose_call( 'PeaqStorage', @@ -23,6 +34,10 @@ class TestPalletStorage(unittest.TestCase): def setUp(self): self._substrate = SubstrateInterface(url=WS_URL) self._kp_src = Keypair.create_from_uri('//Alice') + wait_until_block_height(SubstrateInterface(url=RELAYCHAIN_WS_URL), 1) + wait_until_block_height(SubstrateInterface(url=PARACHAIN_WS_URL), 1) + self._chain_spec = get_chain(self._substrate) + self._chain_spec = get_modified_chain_spec(self._chain_spec) def test_storage(self): reserved_before = get_balance_reserve_value(self._substrate, self._kp_src.ss58_address, 'peaqstor') @@ -40,6 +55,7 @@ def test_storage(self): item) reserved_after = get_balance_reserve_value(self._substrate, self._kp_src.ss58_address, 'peaqstor') self.assertGreater(reserved_after, reserved_before) + self.assertGreaterEqual(reserved_after - reserved_before, STORAGE_MIN_DEPOSIT[self._chain_spec]) def test_storage_update(self): batch = ExtrinsicBatch(self._substrate, self._kp_src) diff --git a/tests/reward_distribution_test.py b/tests/reward_distribution_test.py index 3000a03..350785e 100644 --- a/tests/reward_distribution_test.py +++ b/tests/reward_distribution_test.py @@ -37,6 +37,8 @@ } } +FORCE_KEEP_POT_BALANCE = 10 + COLLATOR_DELEGATOR_POT = '5EYCAe5cKPAoFh2HnQQvpKqRYZGqBpaA87u4Zzw89qPE58is' DIVISION_FACTOR = pow(10, 7) @@ -149,7 +151,8 @@ def _check_block_reward_in_event(self, kp_collator, first_receipt, second_receip get_account_balance(self._substrate, PARACHAIN_STAKING_POT, second_receipt.block_hash) - \ get_existential_deposit(self._substrate) - self.assertEqual(rewarded_number, pot_transferable_balance) + # Force keep the pot balance to avoid event annoy + self.assertEqual(rewarded_number, pot_transferable_balance - FORCE_KEEP_POT_BALANCE) # Check the block reward + transaction fee (Need to check again...) transaction_fee, transaction_tip = 0, 0 @@ -174,7 +177,7 @@ def _check_block_reward_in_event(self, kp_collator, first_receipt, second_receip # Check all collator reward in collators first_balance = get_account_balance(self._substrate, kp_collator.ss58_address, first_new_session_block_hash) second_balance = get_account_balance(self._substrate, kp_collator.ss58_address, second_new_session_block_hash) - self.assertEqual(second_balance - first_balance, pot_transferable_balance) + self.assertEqual(second_balance - first_balance, pot_transferable_balance - FORCE_KEEP_POT_BALANCE) def _check_transaction_fee_reward_from_sender(self, block_height): block_hash = self._substrate.get_block_hash(block_height) diff --git a/tests/token_economy_test.py b/tests/token_economy_test.py index 6b72937..dc00bf0 100644 --- a/tests/token_economy_test.py +++ b/tests/token_economy_test.py @@ -81,9 +81,9 @@ 'module': 'ParachainStaking', 'storage_function': 'MaxCollatorsPerDelegator', 'type': { - 'peaq-dev': 1, - 'krest-network': 1, - 'peaq-network': 1, + 'peaq-dev': 8, + 'krest-network': 8, + 'peaq-network': 8, } }, { 'module': 'ParachainStaking', @@ -247,6 +247,9 @@ def test_total_issuance(self): golden_issuance_number[self._chain_spec], f'{total_balance.value} <= {golden_issuance_number[self._chain_spec]}') + # We only check the total issuance is greater than the golden issuance number because + # when time goes on, the total issuance will be increased. + # Will fail after this runtime upgrade # In the future, after we extract the inflation manager's pot, we will not be able to test this @pytest.mark.skipif(TestUtils.is_local_new_chain() is False, reason='Dont need to test on the new chain') diff --git a/tests/utils_func.py b/tests/utils_func.py index d342cf4..f1cf03c 100644 --- a/tests/utils_func.py +++ b/tests/utils_func.py @@ -42,7 +42,7 @@ def is_not_dev_chain(): def is_not_peaq_chain(): ws = SubstrateInterface(url=WS_URL) chain_name = get_chain(ws) - return chain_name not in ['peaq', 'peaq-network-fork'] + return chain_name not in ['peaq-network', 'peaq-network-fork'] def is_krest_related_chain(): diff --git a/tests/xcm_transfer_test.py b/tests/xcm_transfer_test.py index 9d24854..0a4234d 100644 --- a/tests/xcm_transfer_test.py +++ b/tests/xcm_transfer_test.py @@ -85,6 +85,27 @@ def get_asset_token_location(asset_id): } +def get_asset_pallet_location(asset_id): + if asset_id >= 16: + raise ValueError('Asset id should < 16') + + return { + 'peaq': { + XCM_VER: { + 'parents': '1', + 'interior': { + 'X2': [ + {'Parachain': 3000}, + {'PalletInstance': 10}, + ] + } + } + }, + # Force failed + 'para': {} + } + + def get_metadata(symbol): return { 'name': symbol, @@ -103,6 +124,12 @@ def get_metadata(symbol): TEST_SUFF_ASSET_TOKEN = get_asset_token_location(TEST_SUFF_ASSET_IDX) TEST_SUFF_ASSET_METADATA = get_metadata('suf') +TEST_PALLET_ASSET_IDX = 12 +TEST_PALLET_ASSET_ID = get_test_asset_id(TEST_PALLET_ASSET_IDX) +TEST_PALLET_ASSET_TOKEN = get_asset_pallet_location(TEST_PALLET_ASSET_IDX) +TEST_PALLET_ASSET_METADATA = get_metadata('pal') + + TEST_LP_ASSET_ID = { 'peaq': convert_enum_to_asset_id({'LPToken': [0, 1]}), 'para': 1000, @@ -329,6 +356,7 @@ def test_from_relay_to_peaq(self): kp_self_dst = Keypair.create_from_mnemonic(Keypair.generate_mnemonic()) receipt = fund(self.si_peaq, KP_GLOBAL_SUDO, kp_self_dst, INIT_TOKEN_NUM) self.assertTrue(receipt.is_success, f'Failed to fund account, {receipt.error_message}') + self.si_relay = SubstrateInterface(url=RELAYCHAIN_WS_URL, type_registry_preset='rococo') receipt = fund(self.si_relay, KP_GLOBAL_SUDO, kp_remote_src, INIT_TOKEN_NUM) self.assertTrue(receipt.is_success, f'Failed to fund account, {receipt.error_message}') @@ -481,6 +509,24 @@ def _set_up_peaq_asset_on_peaq_if_not_exist(self, asset_id, kp_para_src, is_suff asset_token['peaq'], UNITS_PER_SECOND) self.assertTrue(receipt.is_success, f'Failed to register foreign asset: {receipt.error_message}') + def _set_up_peaq_pallet_asset_on_peaq_if_not_exist(self, asset_id, kp_para_src): + asset_token = TEST_PALLET_ASSET_TOKEN + kp_self_dst = kp_para_src + batch = ExtrinsicBatch(self.si_peaq, KP_GLOBAL_SUDO) + batch_fund(batch, kp_self_dst, INIT_TOKEN_NUM) + if not self.si_peaq.query("Assets", "Asset", [asset_id]).value: + batch_force_create_asset(batch, KP_GLOBAL_SUDO.ss58_address, asset_id) + batch_set_metadata( + batch, asset_id, + TEST_ASSET_METADATA['name'], TEST_ASSET_METADATA['symbol'], TEST_ASSET_METADATA['decimals']) + batch_mint(batch, self.alice.ss58_address, asset_id, 10 * TEST_TOKEN_NUM) + receipt = batch.execute() + self.assertTrue(receipt.is_success, f'Failed to create asset: {receipt.error_message}') + receipt = setup_xc_register_if_not_exist( + self.si_peaq, KP_GLOBAL_SUDO, asset_id, + asset_token['peaq'], UNITS_PER_SECOND) + self.assertTrue(receipt.is_success, f'Failed to register foreign asset: {receipt.error_message}') + def _check_peaq_asset_from_peaq_to_aca_and_back(self, kp_para_src, kp_self_dst, asset_id, is_sufficient): aca_block_num = self.si_aca.get_block_number(None) receipt = send_token_from_peaq_to_para( @@ -645,3 +691,24 @@ def test_sibling_parachain_delivery_fee(self): # Assert that the delivery fee is within the expected range self.assertGreaterEqual(delivery_fee, TEST_FEES_RANGE[self.chain_spec]['min']) self.assertLessEqual(delivery_fee, TEST_FEES_RANGE[self.chain_spec]['max']) + + # However, cannot receive on the other side + def test_asset_with_pallet_from_peaq_to_aca(self): + # From Alice transfer to kp_para_src (other chain) and move to the kp_self_dst + # Create new asset id and register on peaq + asset_id = TEST_PALLET_ASSET_ID['peaq'] + kp_para_src = Keypair.create_from_mnemonic(Keypair.generate_mnemonic()) + self._set_up_peaq_pallet_asset_on_peaq_if_not_exist(asset_id, kp_para_src) + + batch = ExtrinsicBatch(self.si_peaq, KP_GLOBAL_SUDO) + batch_fund(batch, SOVERIGN_ADDR, INIT_TOKEN_NUM) + receipt = batch.execute() + self.assertTrue(receipt.is_success, f'Failed to create asset: {receipt.error_message}') + + # we won't register on aca because the multilocation is not correct + # we won't need to setup the dst account because we cannot receive it + + receipt = send_token_from_peaq_to_para( + self.si_peaq, self.alice, kp_para_src, + ACA_PD_CHAIN_ID, TEST_PALLET_ASSET_ID['peaq'], TEST_TOKEN_NUM) + self.assertTrue(receipt.is_success, f'Failed to send token from peaq to para chain: {receipt.error_message}') diff --git a/tools/monkey/monkey_3rd_substrate_interface.py b/tools/monkey/monkey_3rd_substrate_interface.py index cb6a71f..597f019 100644 --- a/tools/monkey/monkey_3rd_substrate_interface.py +++ b/tools/monkey/monkey_3rd_substrate_interface.py @@ -28,11 +28,13 @@ def monkey_submit_extrinsic(self, extrinsic: GenericExtrinsic, wait_for_inclusio if not wait_for_inclusion and not wait_for_finalization: return result - time.sleep(BLOCK_GENERATE_TIME * 3) + time.sleep(BLOCK_GENERATE_TIME * 4) now_block_num = self.get_block_number(None) included_block = None tx_identifier = None - for i in range(5): + for i in range(8): + if now_block_num - i < 1 or tx_identifier: + break print(f'Checking block {now_block_num - i}') block_hash = self.get_block_hash(now_block_num - i) block = self.get_block(block_hash) @@ -43,8 +45,6 @@ def monkey_submit_extrinsic(self, extrinsic: GenericExtrinsic, wait_for_inclusio print(f'Extrinsic {result.extrinsic_hash} is included in block {included_block}') tx_identifier = f'{included_block}-{index}' break - if tx_identifier: - break if not tx_identifier: raise SubstrateRequestException( f'Extrinsic {result.extrinsic_hash} is not included in the block after 3 blocks, invalid') diff --git a/tools/monkey/monkey_reorg_batch.py b/tools/monkey/monkey_reorg_batch.py index 811b649..a073471 100644 --- a/tools/monkey/monkey_reorg_batch.py +++ b/tools/monkey/monkey_reorg_batch.py @@ -23,6 +23,13 @@ def _backtrace_blocks_by_extrinsic(substrate, extrinsic_hash): return None +def short_print(batch): + if len(str(batch)) > 512: + return f'{str(batch)[:512]}...' + else: + return f'{batch}' + + # Try to fix the reorg issue by retrying the batch def monkey_execute_extrinsic_batch(self, substrate, kp_src, batch, wait_for_finalization=False, @@ -30,13 +37,13 @@ def monkey_execute_extrinsic_batch(self, substrate, kp_src, batch, RETRY_TIMES = 3 for i in range(RETRY_TIMES): try: - print(f'{substrate.url}, {batch}') + print(f'{substrate.url}, {short_print(batch)}') receipt = origin_execute_extrinsic_batch(self, substrate, kp_src, batch, wait_for_finalization, tip) show_extrinsic(receipt, f'{substrate.url}') return receipt except SubstrateRequestException as e: if 'invalid' in str(e): - print(f'Error: {e}, {batch}') + print(f'Error: {e}, {short_print(batch)}') for i in range(4): print('Wait for 6 seconds') time.sleep(BLOCK_GENERATE_TIME) diff --git a/tools/peaq_eth_utils.py b/tools/peaq_eth_utils.py index 2d18c75..cb23988 100644 --- a/tools/peaq_eth_utils.py +++ b/tools/peaq_eth_utils.py @@ -95,6 +95,7 @@ def get_eth_info(mnemonic=""): mnemonic = Keypair.generate_mnemonic() kp = Keypair.create_from_mnemonic(mnemonic, crypto_type=KeypairType.ECDSA) return { + 'mnemonic': mnemonic, 'kp': kp, 'substrate': calculate_evm_account(kp.ss58_address), 'eth': kp.ss58_address diff --git a/tools/runtime_upgrade.py b/tools/runtime_upgrade.py index 5d93a73..55e7bde 100644 --- a/tools/runtime_upgrade.py +++ b/tools/runtime_upgrade.py @@ -18,6 +18,7 @@ from peaq.utils import get_account_balance from tools.constants import BLOCK_GENERATE_TIME from tools.xcm_setup import setup_hrmp_channel +from tools.utils import show_title import argparse import pprint @@ -148,7 +149,6 @@ def do_runtime_upgrade(wasm_path): upgrade(wasm_path) wait_for_n_blocks(substrate, 15) # Cannot move in front of the upgrade because V4 only exists in 1.7.2 - update_xcm_default_version(substrate) new_version = substrate.get_block_runtime_version(substrate.get_block_hash())['specVersion'] if old_version == new_version: @@ -179,6 +179,7 @@ def main(): if __name__ == '__main__': + show_title('Runtime upgrade') # For the monkey patching to work, the module must be reloaded # Avoid the dependency on the module name if 'substrateinterface' in sys.modules: diff --git a/tools/snapshot_info.py b/tools/snapshot_info.py index b6ac840..cbd1351 100644 --- a/tools/snapshot_info.py +++ b/tools/snapshot_info.py @@ -7,7 +7,7 @@ pp = pprint.PrettyPrinter(indent=4) ENDPOINTS = { - 'peaq-dev': 'wss://wsspc1-qa.agung.peaq.network', + 'peaq-dev': 'wss://wss-async.agung.peaq.network', 'krest': 'wss://wss-krest.peaq.network', 'peaq': 'wss://mpfn1.peaq.network', 'docker': 'wss://docker-test.peaq.network', @@ -45,6 +45,77 @@ 'TransactionPayment': 'all', } +SHEET_INTERESTED_LIST = { + 'InflationManager::InflationConfiguration', + 'InflationManager::InflationParameters', + 'InflationManager::CurrentYear', + 'InflationManager::DoRecalculationAt', + 'InflationManager::DoInitializeAt', + 'InflationManager::TotalIssuanceNum', + 'InflationManager::BlockRewards', + 'ParachainStaking::MaxSelectedCandidates', + 'ParachainStaking::Round', + 'ParachainStaking::CounterForCandidatePool', + 'ParachainStaking::MaxCollatorCandidateStake', + 'ParachainStaking::ForceNewRound', + 'StakingCoefficientRewardCalculator::CoefficientConfig', + # Skip Zenlink protocol + # Skip XcAssetConfig, + # Skip PeaqMor + 'Balances::ExistentialDeposit', + 'Balances::MaxLocks', + 'Balances::MaxReserves', + 'Balances::MaxFreezes', + # Skip Contracts + 'Treasury::ProposalBond', + 'Treasury::ProposalBondMinimum', + 'Treasury::ProposalBondMaximum', + 'Treasury::SpendPeriod', + 'Treasury::Burn', + 'Treasury::MaxApprovals', + 'Treasury::PayoutPeriod', + 'InflationManager::DefaultTotalIssuanceNum', + 'InflationManager::DefaultInflationConfiguration', + 'InflationManager::BoundedDataLen', + 'ParachainStaking::MinBlocksPerRound', + 'ParachainStaking::DefaultBlocksPerRound', + 'ParachainStaking::StakeDuration', + 'ParachainStaking::ExitQueueDelay', + 'ParachainStaking::MinCollators', + 'ParachainStaking::MinRequiredCollators', + 'ParachainStaking::MaxDelegationsPerRound', + 'ParachainStaking::MaxDelegatorsPerCollator', + 'ParachainStaking::MaxCollatorsPerDelegator', + 'ParachainStaking::MaxTopCandidates', + 'ParachainStaking::MinCollatorStake', + 'ParachainStaking::MinCollatorCandidateStake', + 'ParachainStaking::MinDelegation', + 'ParachainStaking::MinDelegatorStake', + 'ParachainStaking::MaxUnstakeRequests', + 'Assets::RemoveItemsLimit', + 'Assets::AssetDeposit', + 'Assets::AssetAccountDeposit', + 'Assets::MetadataDepositBase', + 'Assets::MetadataDepositPerByte', + 'Assets::ApprovalDeposit', + 'Assets::StringLimit', + 'Vesting::MinVestedTransfer', + 'Vesting::MaxVestingSchedules', + 'PeaqDid::BoundedDataLen', + 'PeaqDid::StorageDepositBase', + 'PeaqDid::StorageDepositPerByte', + 'Multisig::DepositBase', + 'Multisig::DepositFactor', + 'Multisig::MaxSignatories', + 'PeaqRbac::BoundedDataLen', + 'PeaqRbac::StorageDepositBase', + 'PeaqRbac::StorageDepositPerByte', + 'PeaqStorage::BoundedDataLen', + 'PeaqStorage::StorageDepositBase', + 'PeaqStorage::StorageDepositPerByte', + 'BlockReward::RewardDistributionConfigStorage', +} + def query_storage(substrate, module, storage_function): try: @@ -52,28 +123,36 @@ def query_storage(substrate, module, storage_function): module=module, storage_function=storage_function, ) - print(f'Querying data: {module}::{storage_function}') + print(f'Querying data: {module}::{storage_function}: {result.value}') return result.value except ValueError: pass - print(f'Querying map: {module}::{storage_function}') result = substrate.query_map( module=module, storage_function=storage_function, max_results=1000, page_size=1000, ) - return {str(k.value): v.value for k, v in result.records} + out = {} + for k, v in result.records: + try: + out[str(k.value)] = v.value + except AttributeError: + out[str(k)] = v.value + print(f'Querying map: {module}::{storage_function}: v.value') + return out def query_constant(substrate, module, storage_function): - print(f'Querying constant: {module}::{storage_function}') result = substrate.get_constant( module, storage_function, ) + if f'{module}::{storage_function}' in SHEET_INTERESTED_LIST: + print(f'Show me the constant: {module}::{storage_function}: {result.value}') + print(f'Querying constant: {module}::{storage_function}: {result.value}') return result.value @@ -87,7 +166,7 @@ def is_storage_ignore(module, storage_function): return False -def get_all_storage(substrate, metadata, out): +def get_all_storage(substrate, metadata, out, interested_out): for pallet in metadata.value[1]['V14']['pallets']: if not pallet['storage']: continue @@ -98,12 +177,15 @@ def get_all_storage(substrate, metadata, out): out[pallet['name']][entry['name']] = 'ignored' continue data = query_storage(substrate, pallet['name'], entry['name']) + if f'{pallet["name"]}::{entry["name"]}' in interested_out: + interested_out[f'{pallet["name"]}::{entry["name"]}'] = data + out[pallet['name']][entry['name']] = data return out -def get_all_constants(substrate, metadata, out): +def get_all_constants(substrate, metadata, out, interested_out): for pallet in metadata.value[1]['V14']['pallets']: if not pallet['constants']: continue @@ -111,6 +193,8 @@ def get_all_constants(substrate, metadata, out): out[pallet['name']] = {} for entry in pallet['constants']: data = query_constant(substrate, pallet['name'], entry['name']) + if f'{pallet["name"]}::{entry["name"]}' in SHEET_INTERESTED_LIST: + interested_out[f'{pallet["name"]}::{entry["name"]}'] = data out[pallet['name']][entry['name']] = data return out @@ -150,11 +234,14 @@ def get_all_constants(substrate, metadata, out): 'storage': {}, } - get_all_storage(substrate, metadata, out['storage']) - get_all_constants(substrate, metadata, out['constants']) + interested_out = {k: None for k in SHEET_INTERESTED_LIST} + get_all_storage(substrate, metadata, out['storage'], interested_out) + get_all_constants(substrate, metadata, out['constants'], interested_out) pp.pprint(out) if args.folder: filepath = f'{args.folder}/{args.runtime}.{substrate.runtime_version}' with open(filepath, 'w') as f: f.write(pp.pformat(out)) + + pp.pprint(interested_out) diff --git a/tools/stress_delegator_multi_collators_evm.py b/tools/stress_delegator_multi_collators_evm.py new file mode 100644 index 0000000..6653423 --- /dev/null +++ b/tools/stress_delegator_multi_collators_evm.py @@ -0,0 +1,262 @@ +import sys +sys.path.append('./') +import time + +from substrateinterface import SubstrateInterface, Keypair +from tools.utils import get_all_events +from peaq.utils import get_block_height, get_block_hash +from peaq.utils import ExtrinsicBatch +from tools.peaq_eth_utils import sign_and_submit_evm_transaction +from tools.constants import KP_GLOBAL_SUDO +from tools.utils import get_collators +from tools.peaq_eth_utils import get_contract +from tools.peaq_eth_utils import get_eth_info +from tools.runtime_upgrade import wait_until_block_height +from tools.peaq_eth_utils import get_eth_chain_id +from web3 import Web3 +import argparse + + +PARACHAIN_STAKING_ABI_FILE = 'ETH/parachain-staking/abi' +PARACHAIN_STAKING_ADDR = '0x0000000000000000000000000000000000000807' + + +def get_collator_stake(substrate: SubstrateInterface, validator: str) -> int: + key = Keypair(ss58_address=validator) + collator_info = get_collators(substrate, key) + return int(collator_info['stake'].value) + + +def get_validators_info(substrate): + validators = substrate.query('Session', 'Validators', []) + return [validator.value for validator in validators] + + +def check_delegator_reward_event(substrate, addr, number): + for i in range(number): + time.sleep(6) + print(f'Check delegator reward event {i}') + block_height = get_block_height(substrate) + block_hash = get_block_hash(substrate, block_height) + result = get_all_events( + substrate, + block_hash, + 'ParachainStaking', 'Rewarded') + reward_info = { + event.value['attributes'][0]: event.value['attributes'][1] + for event in result + } + if addr in reward_info: + print(f'Delegator reward event {addr} found, block height {block_height}, reward {reward_info[addr]}') + continue + else: + print(f'Delegator reward {addr} event {block_height} not found') + return False + return True + + +def wait_next_session(substrate, block_hash): + current_session = substrate.query("Session", "CurrentIndex", [], block_hash) + for i in range(20): + block_hash = get_block_hash(substrate, get_block_height(substrate)) + now_session = substrate.query("Session", "CurrentIndex", [], block_hash) + if now_session != current_session: + print(f'Wait for next session {now_session} > {current_session}') + return + time.sleep(6) + print('Wait for next session failed') + + +def delegate_join_delegators(w3, eth_chain_id, delegators, collator_addr, collator_stake): + contract = get_contract(w3, PARACHAIN_STAKING_ADDR, PARACHAIN_STAKING_ABI_FILE) + for i, eth_kp in enumerate(delegators): + eth_kp_src = eth_kp['kp'] + nonce = w3.eth.get_transaction_count(eth_kp_src.ss58_address) + tx = contract.functions.joinDelegators(collator_addr, collator_stake).build_transaction({ + 'from': eth_kp_src.ss58_address, + 'nonce': nonce, + 'chainId': eth_chain_id}) + + return sign_and_submit_evm_transaction(tx, w3, eth_kp_src) + + +def delegate_delegators(w3, eth_chain_id, delegators, collator_addr, collator_stake): + contract = get_contract(w3, PARACHAIN_STAKING_ADDR, PARACHAIN_STAKING_ABI_FILE) + for i, eth_kp in enumerate(delegators): + eth_kp_src = eth_kp['kp'] + nonce = w3.eth.get_transaction_count(eth_kp_src.ss58_address) + tx = contract.functions.delegateAnotherCandidate(collator_addr, collator_stake).build_transaction({ + 'from': eth_kp_src.ss58_address, + 'nonce': nonce, + 'chainId': eth_chain_id}) + + return sign_and_submit_evm_transaction(tx, w3, eth_kp_src) + + +def delegator_leave_delegators(w3, eth_chain_id, eth_kp_src): + contract = get_contract(w3, PARACHAIN_STAKING_ADDR, PARACHAIN_STAKING_ABI_FILE) + nonce = w3.eth.get_transaction_count(eth_kp_src.ss58_address) + tx = contract.functions.leaveDelegators().build_transaction({ + 'from': eth_kp_src.ss58_address, + 'nonce': nonce, + 'chainId': eth_chain_id}) + + return sign_and_submit_evm_transaction(tx, w3, eth_kp_src) + + +def setup_delegators(substrate, w3, eth_chain_id, kps, validators, collator_nums, number): + # Delegate the first + evm_receipt = delegate_join_delegators( + w3, eth_chain_id, kps, validators[0], collator_nums[0] + ) + if evm_receipt['status'] != 1: + raise IOError('Join delegators failed') + if number == 1: + return + print('Wait for one session') + block_hash = get_block_hash(substrate, evm_receipt['blockNumber']) + batch = ExtrinsicBatch(substrate, KP_GLOBAL_SUDO) + batch.compose_sudo_call( + 'ParachainStaking', + 'force_new_round', + {} + ) + receipt = batch.execute() + if not receipt.is_success: + print('Force new round failed') + raise IOError('Force new round failed') + + wait_next_session(substrate, block_hash) + + # Delegate + validators = validators[1:] + collator_nums = collator_nums[1:] + for idx, validator in enumerate(validators[:number]): + print(F'Setup delegators for {validator} start, {idx} / {len(validators)}') + evm_receipt = delegate_delegators( + w3, + eth_chain_id, + kps, + validator, + collator_nums[idx]) + print(f'Setup delegators for {validator} successfully, {idx} / {len(validators)}') + block_hash = get_block_hash(substrate, evm_receipt['blockNumber']) + batch = ExtrinsicBatch(substrate, KP_GLOBAL_SUDO) + batch.compose_sudo_call( + 'ParachainStaking', + 'force_new_round', + {} + ) + receipt = batch.execute() + if not receipt.is_success: + print('Force new round failed') + raise IOError('Force new round failed') + + wait_next_session(substrate, block_hash) + + +def check_delegator_number(substrate, addr, number): + out = substrate.query('ParachainStaking', 'DelegatorState', [addr]) + + if out.value is None and number == 0: + print(f'Delegator {number} found and okay') + return True + + if out.value is None or 'delegations' not in out.value: + print(f'Delegator number {number} not found') + return False + + if len(out.value['delegations']) == number: + print(f'Delegator number {number} found') + return True + else: + print(f'Delegator number {number} not equal to {out.value["delegators"]}') + return False + + +def main(): # noqa: C901 + parser = argparse.ArgumentParser(description='Setup the delegator') + parser.add_argument('--number', type=int, required=True, help='Number of collators one delegator want to delegate') + parser.add_argument('--ws', type=str, required=True, help='Substrate websocet URL') + parser.add_argument('--rpc', type=str, required=True, help='ETH RPC URL') + + args = parser.parse_args() + substrate = SubstrateInterface(url=args.ws) + w3 = Web3(Web3.HTTPProvider(args.rpc)) + eth_chain_id = get_eth_chain_id(substrate) + + # Setup the delegator + + print('Wait until block height 1') + wait_until_block_height(substrate, 1) + contract = get_contract(w3, PARACHAIN_STAKING_ADDR, PARACHAIN_STAKING_ABI_FILE) + validators = contract.functions.getCollatorList().call() + collator_nums = [validator[1] for validator in validators] + validators = [validator[0] for validator in validators] + if len(validators) == 0: + print('No validators found') + return + if len(validators) < args.number: + print(f'Number of validators {len(validators)} is less than {args.number}') + return + + print(f'Number of validators are {len(validators)}') + + # Change + # Substrate addr: 5Hm8tdzjjPrGRonEsDvZZNdqS3d2brgYQkw7vPchwQYnB7kY + target_kp = get_eth_info( + 'trumpet depth hidden success nominee twenty erode mixture pond bread easily cycle' + ) + kps = [target_kp] + + batch = ExtrinsicBatch(substrate, KP_GLOBAL_SUDO) + batch.compose_sudo_call( + 'ParachainStaking', + 'force_new_round', + {} + ) + receipt = batch.execute() + if not receipt.is_success: + print('Force new round failed') + raise IOError('Force new round failed') + + # Leave the delegators + try: + evm_receipt = delegator_leave_delegators(w3, eth_chain_id, kps[0]['kp']) + print('Leave delegators successfully') + if evm_receipt['status'] != 1: + print('Leave delegators failed') + raise IOError('Leave delegators failed') + except ValueError as e: + print(f'Leave delegators failed {e}') + pass + + for i in range(10000): + print('Setup delegators start {}'.format(i)) + setup_delegators(substrate, w3, eth_chain_id, kps, validators, collator_nums, args.number) + + if not check_delegator_number(substrate, kps[0]['substrate'], args.number): + print('Delegator number not found') + raise IOError('Delegator number not found') + + # Wait and check whether the delegators there + if not check_delegator_reward_event(substrate, kps[0]['substrate'], 100): + print('Delegator reward event not found') + raise IOError('Delegator reward event not found') + + # Leave the delegators + evm_receipt = delegator_leave_delegators(w3, eth_chain_id, kps[0]['kp']) + if evm_receipt['status'] != 1: + print('Leave delegators failed') + raise IOError('Leave delegators failed') + + if not check_delegator_number(substrate, kps[0]['substrate'], 0): + print('Delegator number not found') + raise IOError('Delegator number not found') + + print('Setup delegators successfully {}'.format(i)) + + +# python3 tools/stress_delegator_multi_collators_evm.py --number 8 --ws wss://docker-test.peaq.network --rpc https://docker-test.peaq.network +if __name__ == '__main__': + main() diff --git a/tools/stress_delegator_multi_collators_substrate.py b/tools/stress_delegator_multi_collators_substrate.py new file mode 100644 index 0000000..739fe3e --- /dev/null +++ b/tools/stress_delegator_multi_collators_substrate.py @@ -0,0 +1,221 @@ +import sys +sys.path.append('./') +import time + +from substrateinterface import SubstrateInterface, Keypair +from peaq.sudo_extrinsic import funds +from tools.utils import get_all_events +from peaq.utils import get_block_height, get_block_hash +from peaq.utils import ExtrinsicBatch +from tools.utils import get_collators +from tools.constants import KP_GLOBAL_SUDO +from tools.runtime_upgrade import wait_until_block_height +import argparse + + +def fund_delegators(substrate: SubstrateInterface, delegators: list, amount: int, batch_num: int = 500): + delegators = [kp.ss58_address for kp in delegators] + for i in range(0, len(delegators), batch_num): + print(f'Funding {i} / {len(delegators)}') + funds(substrate, KP_GLOBAL_SUDO, delegators[i:i + batch_num], amount) + print(f'Funded {i} / {len(delegators)}') + + +def generate_delegators(number: int): + return [Keypair.create_from_mnemonic(Keypair.generate_mnemonic()) for _ in range(number)] + + +def get_collator_stake(substrate: SubstrateInterface, validator: str) -> int: + key = Keypair(ss58_address=validator) + collator_info = get_collators(substrate, key) + return int(collator_info['stake'].value) + + +def delegate_join_delegators(substrate: SubstrateInterface, delegators: list, collator_addr: str, collator_stake: int): + for i, kp in enumerate(delegators): + batch = ExtrinsicBatch(substrate, kp) + batch.compose_call( + 'ParachainStaking', + 'join_delegators', + { + 'collator': collator_addr, + 'amount': collator_stake + } + ) + return batch.execute() + + +def delegate_delegators(substrate: SubstrateInterface, delegators: list, collator_addr: str, collator_stake: int): + for i, kp in enumerate(delegators): + batch = ExtrinsicBatch(substrate, kp) + batch.compose_call( + 'ParachainStaking', + 'delegate_another_candidate', + { + 'collator': collator_addr, + 'amount': collator_stake + } + ) + return batch.execute() + + +def get_validators_info(substrate): + validators = substrate.query('Session', 'Validators', []) + return [validator.value for validator in validators] + + +def check_delegator_reward_event(substrate, kp, number): + for i in range(number): + time.sleep(6) + print(f'Check delegator reward event {i}') + block_height = get_block_height(substrate) + block_hash = get_block_hash(substrate, block_height) + result = get_all_events( + substrate, + block_hash, + 'ParachainStaking', 'Rewarded') + reward_info = { + event.value['attributes'][0]: event.value['attributes'][1] + for event in result + } + if kp.ss58_address in reward_info: + print(f'Delegator reward event {kp.ss58_address} found, block height {block_height}, reward {reward_info[kp.ss58_address]}') + continue + else: + print(f'Delegator reward {kp.ss58_address} event {block_height} not found') + return False + return True + + +def wait_next_session(substrate, block_hash): + current_session = substrate.query("Session", "CurrentIndex", [], block_hash) + for i in range(20): + block_hash = get_block_hash(substrate, get_block_height(substrate)) + now_session = substrate.query("Session", "CurrentIndex", [], block_hash) + if now_session != current_session: + print(f'Wait for next session {now_session} > {current_session}') + return + time.sleep(6) + print('Wait for next session failed') + + +def setup_delegators(substrate, kps, validators, number): + # Delegate the first + receipt = delegate_join_delegators(substrate, kps, validators[0], get_collator_stake(substrate, validators[0])) + if number == 1: + return + print('Wait for one session') + # [TODO] Let us skip this, only need to enable in Krest/Peaq docker env + # batch = ExtrinsicBatch(substrate, KP_GLOBAL_SUDO) + # batch.compose_sudo_call( + # 'ParachainStaking', + # 'force_new_round', + # {} + # ) + # receipt = batch.execute() + # if not receipt.is_success: + # print('Force new round failed') + # raise IOError('Force new round failed') + + wait_next_session(substrate, receipt.block_hash) + + # Delegate + validators = validators[1:] + for idx, validator in enumerate(validators[:number]): + print(F'Setup delegators for {validator} start, {idx} / {len(validators)}') + receipt = delegate_delegators( + substrate, + kps, + validator, + get_collator_stake(substrate, validator)) + print(f'Setup delegators for {validator} successfully, {idx} / {len(validators)}') + # [TODO] Let us skip this, only need to enable in Krest/Peaq docker env + # receipt = batch.execute() + # if not receipt.is_success: + # print('Force new round failed') + # raise IOError('Force new round failed') + wait_next_session(substrate, receipt.block_hash) + + +def check_delegator_number(substrate, kp, number): + out = substrate.query('ParachainStaking', 'DelegatorState', [kp.ss58_address]) + + if out.value is None and number == 0: + print(f'Delegator {number} found and okay') + return True + + if out.value is None or 'delegations' not in out.value: + print(f'Delegator number {number} not found') + return False + + if len(out.value['delegations']) == number: + print(f'Delegator number {number} found') + return True + else: + print(f'Delegator number {number} not equal to {out.value["delegators"]}') + return False + + +def main(): + parser = argparse.ArgumentParser(description='Setup the delegator') + parser.add_argument('--number', type=int, required=True, help='Number of collators one delegator want to delegate') + parser.add_argument('--url', type=str, required=True, help='websocket URL') + + args = parser.parse_args() + substrate = SubstrateInterface(url=args.url) + + print('Wait until block height 1') + wait_until_block_height(substrate, 1) + validators = get_validators_info(substrate) + if len(validators) == 0: + print('No validators found') + return + if len(validators) < args.number: + print(f'Number of validators {len(validators)} is less than {args.number}') + return + + print(f'Number of validators are {len(validators)}') + # [TODO] Let us skip this, only need to enable in Krest/Peaq docker env + # batch = ExtrinsicBatch(substrate, KP_GLOBAL_SUDO) + # batch.compose_sudo_call( + # 'ParachainStaking', + # 'force_new_round', + # {} + # ) + # receipt = batch.execute() + # if not receipt.is_success: + # print('Force new round failed') + # raise IOError('Force new round failed') + + kps = [Keypair.create_from_mnemonic('trumpet depth hidden success nominee twenty erode mixture pond bread easily cycle')] + for i in range(10000): + print('Setup delegators start {}'.format(i)) + setup_delegators(substrate, kps, validators, args.number) + + if not check_delegator_number(substrate, kps[0], args.number): + print('Delegator number not found') + raise IOError('Delegator number not found') + + # Wait and check whether the delegators there + if not check_delegator_reward_event(substrate, kps[0], 24): + print('Delegator reward event not found') + raise IOError('Delegator reward event not found') + + # Leave the delegators + batch = ExtrinsicBatch(substrate, kps[0]) + batch.compose_call('ParachainStaking', 'leave_delegators', {}) + receipt = batch.execute() + if not receipt.is_success: + print('Leave delegators failed') + raise IOError('Leave delegators failed') + + if not check_delegator_number(substrate, kps[0], 0): + print('Delegator number not found') + raise IOError('Delegator number not found') + + print('Setup delegators successfully {}'.format(i)) + + +# python3 tools/stress_delegator_multi_collators.py --number 8 --url wss://docker-test.peaq.network +if __name__ == '__main__': + main()