From 9bbf356c6b8faca6314ae46f63ae21fcdcd7e866 Mon Sep 17 00:00:00 2001 From: Daniel Burckhardt Date: Fri, 12 Aug 2022 15:57:57 +0200 Subject: [PATCH] tests(rpc): add filter tests (#1233) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * tests(rpc): add pending transaction filter test * tests(rpc): add block filter and event log test * tests(rpc): simplify to cluster instead of comparing types * tests(rpc): wip filter by address * tests(rpc): add get_logs test * fix flake8 linter * fix flake8 linter * add caching to readme * add caching to readme Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com> --- rpc/namespaces/ethereum/eth/filters/api.go | 8 +- tests/integration_tests/README.md | 55 ++++++++ .../contracts/contracts/Greeter.sol | 20 +++ tests/integration_tests/test_filters.py | 118 ++++++++++++++++++ tests/integration_tests/utils.py | 9 ++ 5 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 tests/integration_tests/README.md create mode 100644 tests/integration_tests/contracts/contracts/Greeter.sol create mode 100644 tests/integration_tests/test_filters.py diff --git a/rpc/namespaces/ethereum/eth/filters/api.go b/rpc/namespaces/ethereum/eth/filters/api.go index 669580a5..0f6cf05d 100644 --- a/rpc/namespaces/ethereum/eth/filters/api.go +++ b/rpc/namespaces/ethereum/eth/filters/api.go @@ -25,13 +25,13 @@ import ( // FilterAPI gathers type FilterAPI interface { - GetLogs(ctx context.Context, crit filters.FilterCriteria) ([]*ethtypes.Log, error) - GetFilterChanges(id rpc.ID) (interface{}, error) - GetFilterLogs(ctx context.Context, id rpc.ID) ([]*ethtypes.Log, error) + NewPendingTransactionFilter() rpc.ID NewBlockFilter() rpc.ID NewFilter(criteria filters.FilterCriteria) (rpc.ID, error) - NewPendingTransactionFilter() rpc.ID + GetFilterChanges(id rpc.ID) (interface{}, error) + GetFilterLogs(ctx context.Context, id rpc.ID) ([]*ethtypes.Log, error) UninstallFilter(id rpc.ID) bool + GetLogs(ctx context.Context, crit filters.FilterCriteria) ([]*ethtypes.Log, error) } // Backend defines the methods requided by the PublicFilterAPI backend diff --git a/tests/integration_tests/README.md b/tests/integration_tests/README.md new file mode 100644 index 00000000..d321b7fb --- /dev/null +++ b/tests/integration_tests/README.md @@ -0,0 +1,55 @@ +# RPC Integration tests + +The RPC integration test suite uses nix for reproducible and configurable +builds allowing to run integration tests using python web3 library against +different Ethermint and [Geth](https://github.com/ethereum/go-ethereum) clients with multiple configurations. + +## Installation + +Nix Multi-user installation: + +``` +sh <(curl -L https://nixos.org/nix/install) --daemon +``` + +Make sure the following line has been added to your shell profile (e.g. ~/.profile): + +``` +source ~/.nix-profile/etc/profile.d/nix.sh +``` + +Then re-login shell, the nix installation is completed. + +For linux: + +``` +sh <(curl -L https://nixos.org/nix/install) --no-daemon +``` + +## Run Local + +First time run (can take a while): + +``` +make run-integration-tests +``` + +Once you've run them once and, you can run: + +``` +nix-shell tests/integration_tests/shell.nix +cd tests/integration_tests +pytest -s -vv +``` + +If you're changing anything on the ethermint rpc, rerun the first command. + + +## Caching + +You can enable Binary Cache to speed up the tests: + +``` +$ nix-env -iA cachix -f https://cachix.org/api/v1/install +$ cachix use ethermint +``` \ No newline at end of file diff --git a/tests/integration_tests/contracts/contracts/Greeter.sol b/tests/integration_tests/contracts/contracts/Greeter.sol new file mode 100644 index 00000000..2f5dbda5 --- /dev/null +++ b/tests/integration_tests/contracts/contracts/Greeter.sol @@ -0,0 +1,20 @@ +pragma solidity >0.5.0; + +contract Greeter { + string public greeting; + + event ChangeGreeting(address from, string value); + + constructor() public { + greeting = "Hello"; + } + + function setGreeting(string memory _greeting) public { + greeting = _greeting; + emit ChangeGreeting(msg.sender, _greeting); + } + + function greet() public view returns (string memory) { + return greeting; + } +} diff --git a/tests/integration_tests/test_filters.py b/tests/integration_tests/test_filters.py new file mode 100644 index 00000000..1f871eee --- /dev/null +++ b/tests/integration_tests/test_filters.py @@ -0,0 +1,118 @@ +import pytest +from web3 import Web3 + +from .utils import ( + ADDRS, + CONTRACTS, + deploy_contract, + send_successful_transaction, + send_transaction, +) + + +def test_pending_transaction_filter(cluster): + w3: Web3 = cluster.w3 + flt = w3.eth.filter("pending") + + # without tx + assert flt.get_new_entries() == [] # GetFilterChanges + + # with tx + txhash = send_successful_transaction(w3) + assert txhash in flt.get_new_entries() + + # without new txs since last call + assert flt.get_new_entries() == [] + + +def test_block_filter(cluster): + w3: Web3 = cluster.w3 + flt = w3.eth.filter("latest") + + # without tx + assert flt.get_new_entries() == [] + + # with tx + send_successful_transaction(w3) + blocks = flt.get_new_entries() + assert len(blocks) >= 1 + + # without new txs since last call + assert flt.get_new_entries() == [] + + +def test_event_log_filter_by_contract(cluster): + w3: Web3 = cluster.w3 + contract = deploy_contract(w3, CONTRACTS["Greeter"]) + assert contract.caller.greet() == "Hello" + + # Create new filter from contract + current_height = hex(w3.eth.get_block_number()) + flt = contract.events.ChangeGreeting.createFilter(fromBlock=current_height) + + # without tx + assert flt.get_new_entries() == [] # GetFilterChanges + assert flt.get_all_entries() == [] # GetFilterLogs + + # with tx + tx = contract.functions.setGreeting("world").buildTransaction() + tx_receipt = send_transaction(w3, tx) + assert tx_receipt.status == 1 + + log = contract.events.ChangeGreeting().processReceipt(tx_receipt)[0] + assert log["event"] == "ChangeGreeting" + + new_entries = flt.get_new_entries() + assert len(new_entries) == 1 + assert new_entries[0] == log + assert contract.caller.greet() == "world" + + # without new txs since last call + assert flt.get_new_entries() == [] + assert flt.get_all_entries() == new_entries + + # Uninstall + assert w3.eth.uninstall_filter(flt.filter_id) + assert not w3.eth.uninstall_filter(flt.filter_id) + with pytest.raises(Exception): + flt.get_all_entries() + + +def test_event_log_filter_by_address(cluster): + w3: Web3 = cluster.w3 + + contract = deploy_contract(w3, CONTRACTS["Greeter"]) + assert contract.caller.greet() == "Hello" + + flt = w3.eth.filter({"address": contract.address}) + flt2 = w3.eth.filter({"address": ADDRS["validator"]}) + + # without tx + assert flt.get_new_entries() == [] # GetFilterChanges + assert flt.get_all_entries() == [] # GetFilterLogs + + # with tx + tx = contract.functions.setGreeting("world").buildTransaction() + receipt = send_transaction(w3, tx) + assert receipt.status == 1 + + assert len(flt.get_new_entries()) == 1 + assert len(flt2.get_new_entries()) == 0 + + +def test_get_logs(cluster): + w3: Web3 = cluster.w3 + + contract = deploy_contract(w3, CONTRACTS["Greeter"]) + + # without tx + assert w3.eth.get_logs({"address": contract.address}) == [] + assert w3.eth.get_logs({"address": ADDRS["validator"]}) == [] + + # with tx + tx = contract.functions.setGreeting("world").buildTransaction() + receipt = send_transaction(w3, tx) + assert receipt.status == 1 + + assert len(w3.eth.get_logs({"address": contract.address})) == 1 + assert len(w3.eth.get_logs({"address": ADDRS["validator"]})) == 0 diff --git a/tests/integration_tests/utils.py b/tests/integration_tests/utils.py index 823a2c6f..8b87840d 100644 --- a/tests/integration_tests/utils.py +++ b/tests/integration_tests/utils.py @@ -24,6 +24,7 @@ ADDRS = {name: account.address for name, account in ACCOUNTS.items()} ETHERMINT_ADDRESS_PREFIX = "ethm" TEST_CONTRACTS = { "TestERC20A": "TestERC20A.sol", + "Greeter": "Greeter.sol", } @@ -140,6 +141,14 @@ def send_transaction(w3, tx, key=KEYS["validator"]): return w3.eth.wait_for_transaction_receipt(txhash) +def send_successful_transaction(w3): + signed = sign_transaction(w3, {"to": ADDRS["community"], "value": 1000}) + txhash = w3.eth.send_raw_transaction(signed.rawTransaction) + receipt = w3.eth.wait_for_transaction_receipt(txhash) + assert receipt.status == 1 + return txhash + + def eth_to_bech32(addr, prefix=ETHERMINT_ADDRESS_PREFIX): bz = bech32.convertbits(HexBytes(addr), 8, 5) return bech32.bech32_encode(prefix, bz)