imp(tests): prune node integration tests (#1212)

* first pass

* extra comment

* fixed pruned node tests. Fix getBalance on pruned. Fix BaseFee on pruned.

* fix tests execution

* check logs on tests

* address pr comments

* address comments

* Update rpc/namespaces/ethereum/eth/api.go

* update error msg check

* fix lint

* fix linter

* fix linter

* fix py lint

* test lint

* fix lint

* pin golangcli version

* pin golanci version

* pin lint to version 0.48

* fix linter

* fix last linter last file

Co-authored-by: ramacarlucho <ramirocarlucho@gmail.com>
Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
Freddy Caceres 2022-08-08 04:17:10 -04:00 committed by GitHub
parent 7331cd2065
commit ccbaf1fe05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 268 additions and 34 deletions

View File

@ -25,7 +25,7 @@ jobs:
go.sum go.sum
- uses: golangci/golangci-lint-action@v3 - uses: golangci/golangci-lint-action@v3
with: with:
version: latest version: v1.48.0
args: --timeout 10m args: --timeout 10m
github-token: ${{ secrets.github_token }} github-token: ${{ secrets.github_token }}
# Check only if there are differences in the source code # Check only if there are differences in the source code

View File

@ -1,4 +1,5 @@
/*Package ante defines the SDK auth module's AnteHandler as well as an internal /*
Package ante defines the SDK auth module's AnteHandler as well as an internal
AnteHandler for an Ethereum transaction (i.e MsgEthereumTx). AnteHandler for an Ethereum transaction (i.e MsgEthereumTx).
During CheckTx, the transaction is passed through a series of During CheckTx, the transaction is passed through a series of

View File

@ -38,12 +38,14 @@ const (
mnemonicEntropySize = 256 mnemonicEntropySize = 256
) )
/* RunAddCmd /*
RunAddCmd
input input
- bip39 mnemonic - bip39 mnemonic
- bip39 passphrase - bip39 passphrase
- bip44 path - bip44 path
- local encryption password - local encryption password
output output
- armor encrypted private key (saved to file) - armor encrypted private key (saved to file)
*/ */

View File

@ -294,6 +294,11 @@ func (e *PublicAPI) GetBalance(address common.Address, blockNrOrHash rpctypes.Bl
return nil, errors.New("invalid balance") return nil, errors.New("invalid balance")
} }
// balance can only be negative in case of pruned node
if val.IsNegative() {
return nil, errors.New("couldn't fetch balance. Node state is pruned")
}
return (*hexutil.Big)(val.BigInt()), nil return (*hexutil.Big)(val.BigInt()), nil
} }

View File

@ -0,0 +1,9 @@
[settings]
# compatible with black
# https://black.readthedocs.io/en/stable/the_black_code_style.html
multi_line_output = 3
include_trailing_comma = True
force_grid_wrap = 0
use_parentheses = True
ensure_newline_before_comments = True
line_length = 88

View File

@ -0,0 +1,12 @@
local config = import 'default.jsonnet';
config {
'ethermint_9000-1'+: {
'app-config'+: {
pruning: 'everything',
'state-sync'+: {
'snapshot-interval': 0,
},
},
},
}

View File

@ -0,0 +1,142 @@
from pathlib import Path
import pytest
from eth_bloom import BloomFilter
from eth_utils import abi, big_endian_to_int
from hexbytes import HexBytes
from web3.datastructures import AttributeDict
from .network import setup_custom_ethermint
from .utils import (
ADDRS,
CONTRACTS,
KEYS,
deploy_contract,
sign_transaction,
w3_wait_for_new_blocks,
)
@pytest.fixture(scope="module")
def pruned(request, tmp_path_factory):
"""start-cronos
params: enable_auto_deployment
"""
yield from setup_custom_ethermint(
tmp_path_factory.mktemp("pruned"),
26900,
Path(__file__).parent / "configs/pruned_node.jsonnet",
)
def test_pruned_node(pruned):
"""
test basic json-rpc apis works in pruned node
"""
w3 = pruned.w3
erc20 = deploy_contract(
w3,
CONTRACTS["TestERC20A"],
key=KEYS["validator"],
)
tx = erc20.functions.transfer(ADDRS["community"], 10).buildTransaction(
{"from": ADDRS["validator"]}
)
signed = sign_transaction(w3, tx, KEYS["validator"])
txhash = w3.eth.send_raw_transaction(signed.rawTransaction)
print("wait for prunning happens")
w3_wait_for_new_blocks(w3, 10)
tx_receipt = w3.eth.get_transaction_receipt(txhash.hex())
assert len(tx_receipt.logs) == 1
expect_log = {
"address": erc20.address,
"topics": [
HexBytes(
abi.event_signature_to_log_topic("Transfer(address,address,uint256)")
),
HexBytes(b"\x00" * 12 + HexBytes(ADDRS["validator"])),
HexBytes(b"\x00" * 12 + HexBytes(ADDRS["community"])),
],
"data": "0x000000000000000000000000000000000000000000000000000000000000000a",
"transactionIndex": 0,
"logIndex": 0,
"removed": False,
}
assert expect_log.items() <= tx_receipt.logs[0].items()
# check get_balance and eth_call don't work on pruned state
# we need to check error message here.
# `get_balance` returns unmarshallJson and thats not what it should
res = w3.eth.get_balance(ADDRS["validator"], "latest")
assert res > 0
pruned_res = pruned.w3.provider.make_request(
"eth_getBalance", [ADDRS["validator"], hex(tx_receipt.blockNumber)]
)
assert "error" in pruned_res
assert (
pruned_res["error"]["message"] == "couldn't fetch balance. Node state is pruned"
)
with pytest.raises(Exception):
erc20.caller(block_identifier=tx_receipt.blockNumber).balanceOf(
ADDRS["validator"]
)
# check block bloom
block = w3.eth.get_block(tx_receipt.blockNumber)
assert "baseFeePerGas" in block
assert block.miner == "0x0000000000000000000000000000000000000000"
bloom = BloomFilter(big_endian_to_int(block.logsBloom))
assert HexBytes(erc20.address) in bloom
for topic in expect_log["topics"]:
assert topic in bloom
tx1 = w3.eth.get_transaction(txhash)
tx2 = w3.eth.get_transaction_by_block(
tx_receipt.blockNumber, tx_receipt.transactionIndex
)
exp_tx = AttributeDict(
{
"from": "0x57f96e6B86CdeFdB3d412547816a82E3E0EbF9D2",
"gas": 51542,
"input": (
"0xa9059cbb000000000000000000000000378c50d9264c63f3f92b806d4ee56e"
"9d86ffb3ec000000000000000000000000000000000000000000000000000000"
"000000000a"
),
"nonce": 2,
"to": erc20.address,
"transactionIndex": 0,
"value": 0,
"type": "0x2",
"accessList": [],
"chainId": "0x2328",
}
)
assert tx1 == tx2
for name in exp_tx.keys():
assert tx1[name] == tx2[name] == exp_tx[name]
logs = w3.eth.get_logs(
{
"fromBlock": tx_receipt.blockNumber,
"toBlock": tx_receipt.blockNumber,
}
)[0]
assert (
"address" in logs
and logs["address"] == "0x68542BD12B41F5D51D6282Ec7D91D7d0D78E4503"
)
assert "topics" in logs and len(logs["topics"]) == 3
assert logs["topics"][0] == HexBytes(
"0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef"
)
assert logs["topics"][1] == HexBytes(
"0x00000000000000000000000057f96e6b86cdefdb3d412547816a82e3e0ebf9d2"
)
assert logs["topics"][2] == HexBytes(
"0x000000000000000000000000378c50d9264c63f3f92b806d4ee56e9d86ffb3ec"
)

View File

@ -1,10 +1,44 @@
import json
import os import os
import socket import socket
import time import time
from pathlib import Path
from dotenv import load_dotenv
from eth_account import Account from eth_account import Account
from web3._utils.transactions import fill_nonce, fill_transaction_defaults from web3._utils.transactions import fill_nonce, fill_transaction_defaults
load_dotenv(Path(__file__).parent.parent.parent / "scripts/.env")
Account.enable_unaudited_hdwallet_features()
ACCOUNTS = {
"validator": Account.from_mnemonic(os.getenv("VALIDATOR1_MNEMONIC")),
"community": Account.from_mnemonic(os.getenv("COMMUNITY_MNEMONIC")),
"signer1": Account.from_mnemonic(os.getenv("SIGNER1_MNEMONIC")),
"signer2": Account.from_mnemonic(os.getenv("SIGNER2_MNEMONIC")),
}
KEYS = {name: account.key for name, account in ACCOUNTS.items()}
ADDRS = {name: account.address for name, account in ACCOUNTS.items()}
ETHERMINT_ADDRESS_PREFIX = "ethm"
TEST_CONTRACTS = {
"TestERC20A": "TestERC20A.sol",
}
def contract_path(name, filename):
return (
Path(__file__).parent
/ "contracts/artifacts/contracts/"
/ filename
/ (name + ".json")
)
CONTRACTS = {
**{
name: contract_path(name, filename) for name, filename in TEST_CONTRACTS.items()
},
}
Account.enable_unaudited_hdwallet_features() Account.enable_unaudited_hdwallet_features()
ACCOUNTS = { ACCOUNTS = {
@ -32,6 +66,29 @@ def wait_for_port(port, host="127.0.0.1", timeout=40.0):
) from ex ) from ex
def w3_wait_for_new_blocks(w3, n):
begin_height = w3.eth.block_number
while True:
time.sleep(0.5)
cur_height = w3.eth.block_number
if cur_height - begin_height >= n:
break
def deploy_contract(w3, jsonfile, args=(), key=KEYS["validator"]):
"""
deploy contract and return the deployed contract instance
"""
acct = Account.from_key(key)
info = json.loads(jsonfile.read_text())
contract = w3.eth.contract(abi=info["abi"], bytecode=info["bytecode"])
tx = contract.constructor(*args).buildTransaction({"from": acct.address})
txreceipt = send_transaction(w3, tx, key)
assert txreceipt.status == 1
address = txreceipt.contractAddress
return w3.eth.contract(address=address, abi=info["abi"])
def fill_defaults(w3, tx): def fill_defaults(w3, tx):
return fill_nonce(w3, fill_transaction_defaults(w3, tx)) return fill_nonce(w3, fill_transaction_defaults(w3, tx))

View File

@ -283,8 +283,13 @@ func (k *Keeper) GetNonce(ctx sdk.Context, addr common.Address) uint64 {
// GetBalance load account's balance of gas token // GetBalance load account's balance of gas token
func (k *Keeper) GetBalance(ctx sdk.Context, addr common.Address) *big.Int { func (k *Keeper) GetBalance(ctx sdk.Context, addr common.Address) *big.Int {
cosmosAddr := sdk.AccAddress(addr.Bytes()) cosmosAddr := sdk.AccAddress(addr.Bytes())
params := k.GetParams(ctx) evmDenom := ""
coin := k.bankKeeper.GetBalance(ctx, cosmosAddr, params.EvmDenom) k.paramSpace.GetIfExists(ctx, types.ParamStoreKeyEVMDenom, &evmDenom)
// if node is pruned, params is empty. Return invalid value
if evmDenom == "" {
return big.NewInt(-1)
}
coin := k.bankKeeper.GetBalance(ctx, cosmosAddr, evmDenom)
return coin.Amount.BigInt() return coin.Amount.BigInt()
} }

View File

@ -178,7 +178,7 @@ func (k Keeper) GetHashFn(ctx sdk.Context) vm.GetHashFunc {
// ApplyTransaction runs and attempts to perform a state transition with the given transaction (i.e Message), that will // ApplyTransaction runs and attempts to perform a state transition with the given transaction (i.e Message), that will
// only be persisted (committed) to the underlying KVStore if the transaction does not fail. // only be persisted (committed) to the underlying KVStore if the transaction does not fail.
// //
// Gas tracking // # Gas tracking
// //
// Ethereum consumes gas according to the EVM opcodes instead of general reads and writes to store. Because of this, the // Ethereum consumes gas according to the EVM opcodes instead of general reads and writes to store. Because of this, the
// state transition needs to ignore the SDK gas consumption mechanism defined by the GasKVStore and instead consume the // state transition needs to ignore the SDK gas consumption mechanism defined by the GasKVStore and instead consume the
@ -309,18 +309,18 @@ func (k *Keeper) ApplyTransaction(ctx sdk.Context, tx *ethtypes.Transaction) (*t
// If the message fails, the VM execution error with the reason will be returned to the client // If the message fails, the VM execution error with the reason will be returned to the client
// and the transaction won't be committed to the store. // and the transaction won't be committed to the store.
// //
// Reverted state // # Reverted state
// //
// The snapshot and rollback are supported by the `statedb.StateDB`. // The snapshot and rollback are supported by the `statedb.StateDB`.
// //
// Different Callers // # Different Callers
// //
// It's called in three scenarios: // It's called in three scenarios:
// 1. `ApplyTransaction`, in the transaction processing flow. // 1. `ApplyTransaction`, in the transaction processing flow.
// 2. `EthCall/EthEstimateGas` grpc query handler. // 2. `EthCall/EthEstimateGas` grpc query handler.
// 3. Called by other native modules directly. // 3. Called by other native modules directly.
// //
// Prechecks and Preprocessing // # Prechecks and Preprocessing
// //
// All relevant state transition prechecks for the MsgEthereumTx are performed on the AnteHandler, // All relevant state transition prechecks for the MsgEthereumTx are performed on the AnteHandler,
// prior to running the transaction against the state. The prechecks run are the following: // prior to running the transaction against the state. The prechecks run are the following:
@ -336,11 +336,11 @@ func (k *Keeper) ApplyTransaction(ctx sdk.Context, tx *ethtypes.Transaction) (*t
// //
// 1. set up the initial access list (iff fork > Berlin) // 1. set up the initial access list (iff fork > Berlin)
// //
// Tracer parameter // # Tracer parameter
// //
// It should be a `vm.Tracer` object or nil, if pass `nil`, it'll create a default one based on keeper options. // It should be a `vm.Tracer` object or nil, if pass `nil`, it'll create a default one based on keeper options.
// //
// Commit parameter // # Commit parameter
// //
// If commit is true, the `StateDB` will be committed, otherwise discarded. // If commit is true, the `StateDB` will be committed, otherwise discarded.
func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, msg core.Message, tracer vm.EVMLogger, commit bool, cfg *types.EVMConfig, txConfig statedb.TxConfig) (*types.MsgEthereumTxResponse, error) { func (k *Keeper) ApplyMessageWithConfig(ctx sdk.Context, msg core.Message, tracer vm.EVMLogger, commit bool, cfg *types.EVMConfig, txConfig statedb.TxConfig) (*types.MsgEthereumTxResponse, error) {

View File

@ -73,6 +73,7 @@ func NewTxDataFromTx(tx *ethtypes.Transaction) (TxData, error) {
// //
// - {0,1} + CHAIN_ID * 2 + 35, if EIP155 is used // - {0,1} + CHAIN_ID * 2 + 35, if EIP155 is used
// - {0,1} + 27, otherwise // - {0,1} + 27, otherwise
//
// Ref: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md // Ref: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
func DeriveChainID(v *big.Int) *big.Int { func DeriveChainID(v *big.Int) *big.Int {
if v == nil || v.Sign() < 1 { if v == nil || v.Sign() < 1 {