From 9418af9c8735fb72790ad3c65fa9d1a3cdef04f2 Mon Sep 17 00:00:00 2001 From: Niven Date: Mon, 28 Aug 2023 13:38:13 +0800 Subject: [PATCH 1/7] Add loop contract and mempool block size limit test --- test/functional/contracts/Loop.sol | 12 +++++++ test/functional/feature_evm.py | 54 +++++++++++++++++++++++++++--- 2 files changed, 62 insertions(+), 4 deletions(-) create mode 100644 test/functional/contracts/Loop.sol diff --git a/test/functional/contracts/Loop.sol b/test/functional/contracts/Loop.sol new file mode 100644 index 0000000000..d587fa2633 --- /dev/null +++ b/test/functional/contracts/Loop.sol @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity >=0.7.0 <0.9.0; + +contract Loop { + function loop(uint256 num) public { + uint256 number = 0; + while (number < num) { + number++; + } + } +} diff --git a/test/functional/feature_evm.py b/test/functional/feature_evm.py index 642bde57f3..d5d4fa8209 100755 --- a/test/functional/feature_evm.py +++ b/test/functional/feature_evm.py @@ -4,6 +4,7 @@ # Distributed under the MIT software license, see the accompanying # file LICENSE or http://www.opensource.org/licenses/mit-license.php. """Test EVM behaviour""" +from test_framework.evm_contract import EVMContract from test_framework.evm_key_pair import EvmKeyPair from test_framework.test_framework import DefiTestFramework from test_framework.util import ( @@ -87,6 +88,9 @@ def run_test(self): # Multiple mempool fee replacement self.multiple_eth_rbf() + # Multiple mempool fee replacement + self.mempool_block_limit() + # Test that node should not crash without chainId param self.test_tx_without_chainid() @@ -134,11 +138,11 @@ def erc55_wallet_support(self): self.address = self.nodes[0].get_genesis_keys().ownerAuthAddress self.eth_address = "0x9b8a4af42140d8a4c153a822f02571a1dd037e89" self.eth_address_bech32 = "bcrt1qta8meuczw0mhqupzjl5wplz47xajz0dn0wxxr8" - eth_address_privkey = ( + self.eth_address_privkey = ( "af990cc3ba17e776f7f57fcc59942a82846d75833fa17d2ba59ce6858d886e23" ) self.to_address = "0x6c34cbb9219d8caa428835d2073e8ec88ba0a110" - to_address_privkey = ( + self.to_address_privkey = ( "17b8cb134958b3d8422b6c43b0732fcdb8c713b524df2d45de12f0c7e214ba35" ) @@ -242,8 +246,8 @@ def erc55_wallet_support(self): assert_equal(addr_info["witness_version"], 16) # Import addresses - self.nodes[0].importprivkey(eth_address_privkey) # eth_address - self.nodes[0].importprivkey(to_address_privkey) # self.to_address + self.nodes[0].importprivkey(self.eth_address_privkey) # eth_address + self.nodes[0].importprivkey(self.to_address_privkey) # self.to_address # Generate chain self.nodes[0].generate(101) @@ -1204,6 +1208,48 @@ def multiple_eth_rbf(self): assert_equal(block_txs[1], tx0) assert_equal(block_txs[2], tx1) + def mempool_block_limit(self): + abi, bytecode = EVMContract.from_file("Loop.sol", "Loop").compile() + compiled = self.nodes[0].w3.eth.contract(abi=abi, bytecode=bytecode) + tx = compiled.constructor().build_transaction( + { + "chainId": self.nodes[0].w3.eth.chain_id, + "nonce": self.nodes[0].w3.eth.get_transaction_count(self.eth_address), + "maxFeePerGas": 10_000_000_000, + "maxPriorityFeePerGas": 1_500_000_000, + "gas": 1_000_000, + } + ) + signed = self.nodes[0].w3.eth.account.sign_transaction(tx, self.eth_address_privkey) + hash = self.nodes[0].w3.eth.send_raw_transaction(signed.rawTransaction) + self.nodes[0].generate(1) + receipt = self.nodes[0].w3.eth.wait_for_transaction_receipt(hash) + contract = self.nodes[0].w3.eth.contract(address=receipt["contractAddress"], abi=abi) + + count = self.nodes[0].w3.eth.get_transaction_count(self.eth_address) + for i in range(30): + # tx call actual used gas - 1_761_626. + # tx should always pass evm tx validation, but may return early in construct block. + tx = contract.functions.loop(10_000).build_transaction( + { + "chainId": self.nodes[0].w3.eth.chain_id, + "nonce": count + i, + "gasPrice": 10_000_000_000, + "gas": 30_000_000, + } + ) + signed = self.nodes[0].w3.eth.account.sign_transaction(tx, self.eth_address_privkey) + hash = self.nodes[0].w3.eth.send_raw_transaction(signed.rawTransaction) + + # check that 17 of the 30 evm txs should have been minted in the current block for + # block size limit = 30_000_000 + self.nodes[0].generate(1) + assert_equal(len(self.nodes[0].getblock(self.nodes[0].getbestblockhash())["tx"]) - 1, 17) + + # check that remaining 13 evm txs will be minted in the next block + self.nodes[0].generate(1) + # assert_equal(len(self.nodes[0].getblock(self.nodes[0].getbestblockhash())["tx"]) - 1, 13) + def toggle_evm_enablement(self): # Deactivate EVM self.nodes[0].setgov({"ATTRIBUTES": {"v0/params/feature/evm": "false"}}) From c38161e54e3acbdfce35991beb4c7172de50eb9d Mon Sep 17 00:00:00 2001 From: Niven Date: Mon, 28 Aug 2023 15:47:26 +0800 Subject: [PATCH 2/7] Fix tx fees --- lib/ain-evm/src/evm.rs | 4 +++- test/functional/feature_evm.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/ain-evm/src/evm.rs b/lib/ain-evm/src/evm.rs index 076c894166..11e28a9ee6 100644 --- a/lib/ain-evm/src/evm.rs +++ b/lib/ain-evm/src/evm.rs @@ -151,6 +151,7 @@ impl EVMServices { block.header.state_root }); + debug!("[construct_block] queue_id: {:?}", queue_id); debug!("[construct_block] beneficiary: {:?}", beneficiary); let (vicinity, parent_hash, current_block_number) = match parent_data { None => ( @@ -211,6 +212,7 @@ impl EVMServices { executor.update_storage(address, storage)?; } + debug!("[construct_block] Processing {:?} transactions in queue", queue.transactions.len()); for queue_item in queue.transactions.clone() { match queue_item.tx { QueueTx::SignedTx(signed_tx) => { @@ -461,7 +463,7 @@ impl EVMServices { .core .get_latest_contract_storage(address, ain_contracts::u256_to_h256(U256::one()))?; - debug!("Count: {:#x}", count + U256::one()); + debug!("[counter_contract] count: {:#x}", count + U256::one()); Ok(DeployContractInfo { address, diff --git a/test/functional/feature_evm.py b/test/functional/feature_evm.py index d5d4fa8209..fc7bab660f 100755 --- a/test/functional/feature_evm.py +++ b/test/functional/feature_evm.py @@ -1234,7 +1234,7 @@ def mempool_block_limit(self): { "chainId": self.nodes[0].w3.eth.chain_id, "nonce": count + i, - "gasPrice": 10_000_000_000, + "gasPrice": 20_000_000_000, "gas": 30_000_000, } ) @@ -1248,7 +1248,7 @@ def mempool_block_limit(self): # check that remaining 13 evm txs will be minted in the next block self.nodes[0].generate(1) - # assert_equal(len(self.nodes[0].getblock(self.nodes[0].getbestblockhash())["tx"]) - 1, 13) + assert_equal(len(self.nodes[0].getblock(self.nodes[0].getbestblockhash())["tx"]) - 1, 13) def toggle_evm_enablement(self): # Deactivate EVM From 3c0d826028c6afa3bd28769a894430579bc06f9d Mon Sep 17 00:00:00 2001 From: Niven Date: Mon, 28 Aug 2023 15:48:34 +0800 Subject: [PATCH 3/7] Py lint --- test/functional/feature_evm.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/test/functional/feature_evm.py b/test/functional/feature_evm.py index fc7bab660f..eed2e4e36f 100755 --- a/test/functional/feature_evm.py +++ b/test/functional/feature_evm.py @@ -1220,11 +1220,15 @@ def mempool_block_limit(self): "gas": 1_000_000, } ) - signed = self.nodes[0].w3.eth.account.sign_transaction(tx, self.eth_address_privkey) + signed = self.nodes[0].w3.eth.account.sign_transaction( + tx, self.eth_address_privkey + ) hash = self.nodes[0].w3.eth.send_raw_transaction(signed.rawTransaction) self.nodes[0].generate(1) receipt = self.nodes[0].w3.eth.wait_for_transaction_receipt(hash) - contract = self.nodes[0].w3.eth.contract(address=receipt["contractAddress"], abi=abi) + contract = self.nodes[0].w3.eth.contract( + address=receipt["contractAddress"], abi=abi + ) count = self.nodes[0].w3.eth.get_transaction_count(self.eth_address) for i in range(30): @@ -1238,17 +1242,23 @@ def mempool_block_limit(self): "gas": 30_000_000, } ) - signed = self.nodes[0].w3.eth.account.sign_transaction(tx, self.eth_address_privkey) + signed = self.nodes[0].w3.eth.account.sign_transaction( + tx, self.eth_address_privkey + ) hash = self.nodes[0].w3.eth.send_raw_transaction(signed.rawTransaction) # check that 17 of the 30 evm txs should have been minted in the current block for # block size limit = 30_000_000 self.nodes[0].generate(1) - assert_equal(len(self.nodes[0].getblock(self.nodes[0].getbestblockhash())["tx"]) - 1, 17) + assert_equal( + len(self.nodes[0].getblock(self.nodes[0].getbestblockhash())["tx"]) - 1, 17 + ) # check that remaining 13 evm txs will be minted in the next block self.nodes[0].generate(1) - assert_equal(len(self.nodes[0].getblock(self.nodes[0].getbestblockhash())["tx"]) - 1, 13) + assert_equal( + len(self.nodes[0].getblock(self.nodes[0].getbestblockhash())["tx"]) - 1, 13 + ) def toggle_evm_enablement(self): # Deactivate EVM From af6e69612d205e9a1b234e807d80b6af26a99871 Mon Sep 17 00:00:00 2001 From: Niven Date: Mon, 28 Aug 2023 15:49:46 +0800 Subject: [PATCH 4/7] Fix rust lint --- lib/ain-evm/src/evm.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/ain-evm/src/evm.rs b/lib/ain-evm/src/evm.rs index 11e28a9ee6..0110bb779d 100644 --- a/lib/ain-evm/src/evm.rs +++ b/lib/ain-evm/src/evm.rs @@ -212,7 +212,10 @@ impl EVMServices { executor.update_storage(address, storage)?; } - debug!("[construct_block] Processing {:?} transactions in queue", queue.transactions.len()); + debug!( + "[construct_block] Processing {:?} transactions in queue", + queue.transactions.len() + ); for queue_item in queue.transactions.clone() { match queue_item.tx { QueueTx::SignedTx(signed_tx) => { From 1d5a42329584e9047be7ae8f2e3b2a09c2af53b3 Mon Sep 17 00:00:00 2001 From: Niven Date: Mon, 28 Aug 2023 16:16:28 +0800 Subject: [PATCH 5/7] Add more evm txs in the mempool --- test/functional/feature_evm.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/test/functional/feature_evm.py b/test/functional/feature_evm.py index eed2e4e36f..a990d31c04 100755 --- a/test/functional/feature_evm.py +++ b/test/functional/feature_evm.py @@ -1231,14 +1231,14 @@ def mempool_block_limit(self): ) count = self.nodes[0].w3.eth.get_transaction_count(self.eth_address) - for i in range(30): + for i in range(40): # tx call actual used gas - 1_761_626. # tx should always pass evm tx validation, but may return early in construct block. tx = contract.functions.loop(10_000).build_transaction( { "chainId": self.nodes[0].w3.eth.chain_id, - "nonce": count + i, - "gasPrice": 20_000_000_000, + "nonce": count, + "gasPrice": 25_000_000_000, "gas": 30_000_000, } ) @@ -1246,18 +1246,28 @@ def mempool_block_limit(self): tx, self.eth_address_privkey ) hash = self.nodes[0].w3.eth.send_raw_transaction(signed.rawTransaction) + count = count + 1 - # check that 17 of the 30 evm txs should have been minted in the current block for - # block size limit = 30_000_000 + tx = self.nodes[0].evmtx(self.eth_address, count, 21, 21001, self.to_address, 1) + count = count + 1 + self.nodes[0].evmtx(self.eth_address, count, 21, 21001, self.to_address, 1) + + # check that 17 of the 30 evm contract call txs should have been minted in the current block + self.nodes[0].generate(1) + assert_equal( + len(self.nodes[0].getblock(self.nodes[0].getbestblockhash())["tx"]) - 1, 17 + ) + + # check that 17 of the evm contract call txs will be minted in the next block self.nodes[0].generate(1) assert_equal( len(self.nodes[0].getblock(self.nodes[0].getbestblockhash())["tx"]) - 1, 17 ) - # check that remaining 13 evm txs will be minted in the next block + # check that the remaining evm txs should be minted into this block self.nodes[0].generate(1) assert_equal( - len(self.nodes[0].getblock(self.nodes[0].getbestblockhash())["tx"]) - 1, 13 + len(self.nodes[0].getblock(self.nodes[0].getbestblockhash())["tx"]) - 1, 8 ) def toggle_evm_enablement(self): From a724c9ed67508eab17ae7e35e6ad2ab06d53f101 Mon Sep 17 00:00:00 2001 From: Niven Date: Mon, 28 Aug 2023 17:36:27 +0800 Subject: [PATCH 6/7] Add assertions to ensure minting evm txs ordering --- test/functional/feature_evm.py | 91 +++++++++++++++++++++++++++------- 1 file changed, 72 insertions(+), 19 deletions(-) diff --git a/test/functional/feature_evm.py b/test/functional/feature_evm.py index a990d31c04..dece3ffb5a 100755 --- a/test/functional/feature_evm.py +++ b/test/functional/feature_evm.py @@ -1230,14 +1230,15 @@ def mempool_block_limit(self): address=receipt["contractAddress"], abi=abi ) - count = self.nodes[0].w3.eth.get_transaction_count(self.eth_address) + hashes = [] + start_nonce = self.nodes[0].w3.eth.get_transaction_count(self.eth_address) for i in range(40): # tx call actual used gas - 1_761_626. # tx should always pass evm tx validation, but may return early in construct block. tx = contract.functions.loop(10_000).build_transaction( { "chainId": self.nodes[0].w3.eth.chain_id, - "nonce": count, + "nonce": start_nonce + i, "gasPrice": 25_000_000_000, "gas": 30_000_000, } @@ -1246,29 +1247,81 @@ def mempool_block_limit(self): tx, self.eth_address_privkey ) hash = self.nodes[0].w3.eth.send_raw_transaction(signed.rawTransaction) - count = count + 1 + hashes.append(signed.hash.hex().lower()[2:]) - tx = self.nodes[0].evmtx(self.eth_address, count, 21, 21001, self.to_address, 1) - count = count + 1 - self.nodes[0].evmtx(self.eth_address, count, 21, 21001, self.to_address, 1) - - # check that 17 of the 30 evm contract call txs should have been minted in the current block - self.nodes[0].generate(1) - assert_equal( - len(self.nodes[0].getblock(self.nodes[0].getbestblockhash())["tx"]) - 1, 17 + hash = self.nodes[0].eth_sendTransaction( + { + "nonce": hex(start_nonce + 40), + "from": self.eth_address, + "to": self.to_address, + "value": "0xDE0B6B3A7640000", # 1 DFI + "gas": "0x5209", + "gasPrice": "0x5D21DBA00", # 25_000_000_000 + } + ) + hashes.append(hash.lower()[2:]) + hash = self.nodes[0].eth_sendTransaction( + { + "nonce": hex(start_nonce + 41), + "from": self.eth_address, + "to": self.to_address, + "value": "0xDE0B6B3A7640000", # 1 DFI + "gas": "0x5209", + "gasPrice": "0x5D21DBA00", # 25_000_000_000 + } ) + hashes.append(hash.lower()[2:]) + + first_block_total_txs = 17 + second_block_total_txs = 17 + third_block_total_txs = 8 - # check that 17 of the evm contract call txs will be minted in the next block self.nodes[0].generate(1) - assert_equal( - len(self.nodes[0].getblock(self.nodes[0].getbestblockhash())["tx"]) - 1, 17 - ) + block_info = self.nodes[0].getblock(self.nodes[0].getbestblockhash(), 4) + # check that the first 17 evm contract call txs is minted in the current block + assert_equal(len(block_info["tx"]) - 1, first_block_total_txs) + for idx, tx_info in enumerate(block_info["tx"][1:]): + if idx == 0: + continue + assert_equal(tx_info["vm"]["vmtype"], "evm") + assert_equal(tx_info["vm"]["txtype"], "EvmTx") + assert_equal(tx_info["vm"]["msg"]["sender"], self.eth_address) + assert_equal(tx_info["vm"]["msg"]["nonce"], start_nonce + idx) + assert_equal(tx_info["vm"]["msg"]["hash"], hashes[idx]) + assert_equal(tx_info["vm"]["msg"]["to"], receipt["contractAddress"].lower()) - # check that the remaining evm txs should be minted into this block self.nodes[0].generate(1) - assert_equal( - len(self.nodes[0].getblock(self.nodes[0].getbestblockhash())["tx"]) - 1, 8 - ) + block_info = self.nodes[0].getblock(self.nodes[0].getbestblockhash(), 4) + # check that the next 17 of the evm contract call txs is minted in the next block + assert_equal(len(block_info["tx"]) - 1, second_block_total_txs) + for idx, tx_info in enumerate(block_info["tx"][1:]): + assert_equal(tx_info["vm"]["vmtype"], "evm") + assert_equal(tx_info["vm"]["txtype"], "EvmTx") + assert_equal(tx_info["vm"]["msg"]["sender"], self.eth_address) + assert_equal(tx_info["vm"]["msg"]["nonce"], start_nonce + first_block_total_txs + idx) + assert_equal(tx_info["vm"]["msg"]["hash"], hashes[first_block_total_txs + idx]) + assert_equal(tx_info["vm"]["msg"]["to"], receipt["contractAddress"].lower()) + + # check that the remaining evm txs is minted into this block + self.nodes[0].generate(1) + block_info = self.nodes[0].getblock(self.nodes[0].getbestblockhash(), 4) + assert_equal(len(block_info["tx"]) - 1, third_block_total_txs) + # check ordering of evm txs - first 6 evm txs are evm contract all txs + tx_infos = block_info["tx"][1:] + for idx in range(1, (40 - first_block_total_txs - second_block_total_txs)): + assert_equal(tx_infos[idx]["vm"]["vmtype"], "evm") + assert_equal(tx_infos[idx]["vm"]["txtype"], "EvmTx") + assert_equal(tx_infos[idx]["vm"]["msg"]["sender"], self.eth_address) + assert_equal(tx_infos[idx]["vm"]["msg"]["nonce"], start_nonce + first_block_total_txs + second_block_total_txs + idx) + assert_equal(tx_infos[idx]["vm"]["msg"]["hash"], hashes[first_block_total_txs + second_block_total_txs + idx]) + assert_equal(tx_infos[idx]["vm"]["msg"]["to"], receipt["contractAddress"].lower()) + for idx in range(6, third_block_total_txs): + assert_equal(tx_infos[idx]["vm"]["vmtype"], "evm") + assert_equal(tx_infos[idx]["vm"]["txtype"], "EvmTx") + assert_equal(tx_infos[idx]["vm"]["msg"]["sender"], self.eth_address) + assert_equal(tx_infos[idx]["vm"]["msg"]["nonce"], start_nonce + first_block_total_txs + second_block_total_txs + idx) + assert_equal(tx_infos[idx]["vm"]["msg"]["hash"], hashes[first_block_total_txs + second_block_total_txs + idx]) + assert_equal(tx_infos[idx]["vm"]["msg"]["to"], self.to_address) def toggle_evm_enablement(self): # Deactivate EVM From 8e01b62ce68707cee731afb4fb7dd2aa3427b54e Mon Sep 17 00:00:00 2001 From: Niven Date: Mon, 28 Aug 2023 17:37:20 +0800 Subject: [PATCH 7/7] Fix py lint --- test/functional/feature_evm.py | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/test/functional/feature_evm.py b/test/functional/feature_evm.py index dece3ffb5a..3584a3cb6e 100755 --- a/test/functional/feature_evm.py +++ b/test/functional/feature_evm.py @@ -1298,8 +1298,12 @@ def mempool_block_limit(self): assert_equal(tx_info["vm"]["vmtype"], "evm") assert_equal(tx_info["vm"]["txtype"], "EvmTx") assert_equal(tx_info["vm"]["msg"]["sender"], self.eth_address) - assert_equal(tx_info["vm"]["msg"]["nonce"], start_nonce + first_block_total_txs + idx) - assert_equal(tx_info["vm"]["msg"]["hash"], hashes[first_block_total_txs + idx]) + assert_equal( + tx_info["vm"]["msg"]["nonce"], start_nonce + first_block_total_txs + idx + ) + assert_equal( + tx_info["vm"]["msg"]["hash"], hashes[first_block_total_txs + idx] + ) assert_equal(tx_info["vm"]["msg"]["to"], receipt["contractAddress"].lower()) # check that the remaining evm txs is minted into this block @@ -1312,15 +1316,29 @@ def mempool_block_limit(self): assert_equal(tx_infos[idx]["vm"]["vmtype"], "evm") assert_equal(tx_infos[idx]["vm"]["txtype"], "EvmTx") assert_equal(tx_infos[idx]["vm"]["msg"]["sender"], self.eth_address) - assert_equal(tx_infos[idx]["vm"]["msg"]["nonce"], start_nonce + first_block_total_txs + second_block_total_txs + idx) - assert_equal(tx_infos[idx]["vm"]["msg"]["hash"], hashes[first_block_total_txs + second_block_total_txs + idx]) - assert_equal(tx_infos[idx]["vm"]["msg"]["to"], receipt["contractAddress"].lower()) + assert_equal( + tx_infos[idx]["vm"]["msg"]["nonce"], + start_nonce + first_block_total_txs + second_block_total_txs + idx, + ) + assert_equal( + tx_infos[idx]["vm"]["msg"]["hash"], + hashes[first_block_total_txs + second_block_total_txs + idx], + ) + assert_equal( + tx_infos[idx]["vm"]["msg"]["to"], receipt["contractAddress"].lower() + ) for idx in range(6, third_block_total_txs): assert_equal(tx_infos[idx]["vm"]["vmtype"], "evm") assert_equal(tx_infos[idx]["vm"]["txtype"], "EvmTx") assert_equal(tx_infos[idx]["vm"]["msg"]["sender"], self.eth_address) - assert_equal(tx_infos[idx]["vm"]["msg"]["nonce"], start_nonce + first_block_total_txs + second_block_total_txs + idx) - assert_equal(tx_infos[idx]["vm"]["msg"]["hash"], hashes[first_block_total_txs + second_block_total_txs + idx]) + assert_equal( + tx_infos[idx]["vm"]["msg"]["nonce"], + start_nonce + first_block_total_txs + second_block_total_txs + idx, + ) + assert_equal( + tx_infos[idx]["vm"]["msg"]["hash"], + hashes[first_block_total_txs + second_block_total_txs + idx], + ) assert_equal(tx_infos[idx]["vm"]["msg"]["to"], self.to_address) def toggle_evm_enablement(self):