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
@ -59,7 +59,7 @@ jobs:
PATTERNS: | PATTERNS: |
**/**.py **/**.py
- run: | - run: |
nix-shell -I nixpkgs=./nix -p test-env --run "make lint-py" nix-shell -I nixpkgs=./nix -p test-env --run "make lint-py"
if: env.GIT_DIFF if: env.GIT_DIFF
gomod2nix: gomod2nix:
name: Check gomod2nix.toml file is up to date name: Check gomod2nix.toml file is up to date

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

@ -62,7 +62,7 @@ func (app *EthermintApp) ExportAppStateAndValidators(
// prepare for fresh start at zero height // prepare for fresh start at zero height
// NOTE zero height genesis is a temporary feature which will be deprecated // NOTE zero height genesis is a temporary feature which will be deprecated
// in favor of export at a block height // in favor of export at a block height
func (app *EthermintApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) error { func (app *EthermintApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []string) error {
applyAllowedAddrs := false applyAllowedAddrs := false

View File

@ -154,7 +154,7 @@ func TestAppImportExport(t *testing.T) {
fmt.Printf("importing genesis...\n") fmt.Printf("importing genesis...\n")
// nolint: dogsled //nolint: dogsled
_, newDB, newDir, _, _, err := simapp.SetupSimulation("leveldb-app-sim-2", "Simulation-2") _, newDB, newDir, _, _, err := simapp.SetupSimulation("leveldb-app-sim-2", "Simulation-2")
require.NoError(t, err, "simulation setup failed") require.NoError(t, err, "simulation setup failed")

View File

@ -38,14 +38,16 @@ 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)
*/ */
func RunAddCmd(ctx client.Context, cmd *cobra.Command, args []string, inBuf *bufio.Reader) error { func RunAddCmd(ctx client.Context, cmd *cobra.Command, args []string, inBuf *bufio.Reader) error {
var err error var err error

View File

@ -40,7 +40,7 @@ func SetBech32Prefixes(config *sdk.Config) {
func SetBip44CoinType(config *sdk.Config) { func SetBip44CoinType(config *sdk.Config) {
config.SetCoinType(ethermint.Bip44CoinType) config.SetCoinType(ethermint.Bip44CoinType)
config.SetPurpose(sdk.Purpose) // Shared config.SetPurpose(sdk.Purpose) // Shared
config.SetFullFundraiserPath(ethermint.BIP44HDPath) // nolint: staticcheck config.SetFullFundraiserPath(ethermint.BIP44HDPath) //nolint: staticcheck
} }
// RegisterDenoms registers the base and display denominations to the SDK. // RegisterDenoms registers the base and display denominations to the SDK.

View File

@ -22,7 +22,7 @@ import (
) )
// BackendI implements the Cosmos and EVM backend. // BackendI implements the Cosmos and EVM backend.
type BackendI interface { // nolint: revive type BackendI interface { //nolint: revive
CosmosBackend CosmosBackend
EVMBackend EVMBackend
} }

View File

@ -287,7 +287,7 @@ func (a *API) BlockProfile(file string, nsec uint) error {
// CpuProfile turns on CPU profiling for nsec seconds and writes // CpuProfile turns on CPU profiling for nsec seconds and writes
// profile data to file. // profile data to file.
func (a *API) CpuProfile(file string, nsec uint) error { // nolint: golint, stylecheck, revive func (a *API) CpuProfile(file string, nsec uint) error { //nolint: golint, stylecheck, revive
a.logger.Debug("debug_cpuProfile", "file", file, "nsec", nsec) a.logger.Debug("debug_cpuProfile", "file", file, "nsec", nsec)
if err := a.StartCPUProfile(file); err != nil { if err := a.StartCPUProfile(file); err != nil {
return err return err

View File

@ -128,7 +128,7 @@ func (e *PublicAPI) ProtocolVersion() hexutil.Uint {
} }
// ChainId is the EIP-155 replay-protection chain id for the current ethereum chain config. // ChainId is the EIP-155 replay-protection chain id for the current ethereum chain config.
func (e *PublicAPI) ChainId() (*hexutil.Big, error) { // nolint func (e *PublicAPI) ChainId() (*hexutil.Big, error) { //nolint
e.logger.Debug("eth_chainId") e.logger.Debug("eth_chainId")
// if current block is at or past the EIP-155 replay-protection fork block, return chainID from config // if current block is at or past the EIP-155 replay-protection fork block, return chainID from config
bn, err := e.backend.BlockNumber() bn, err := e.backend.BlockNumber()
@ -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

@ -48,7 +48,7 @@ func NewRangeFilter(logger log.Logger, backend Backend, begin, end int64, addres
// Flatten the address and topic filter clauses into a single bloombits filter // Flatten the address and topic filter clauses into a single bloombits filter
// system. Since the bloombits are not positional, nil topics are permitted, // system. Since the bloombits are not positional, nil topics are permitted,
// which get flattened into a nil byte slice. // which get flattened into a nil byte slice.
var filtersBz [][][]byte // nolint: prealloc var filtersBz [][][]byte //nolint: prealloc
if len(addresses) > 0 { if len(addresses) > 0 {
filter := make([][]byte, len(addresses)) filter := make([][]byte, len(addresses))
for i, address := range addresses { for i, address := range addresses {

View File

@ -15,9 +15,9 @@ import (
) )
// QueryClient defines a gRPC Client used for: // QueryClient defines a gRPC Client used for:
// - Transaction simulation // - Transaction simulation
// - EVM module queries // - EVM module queries
// - Fee market module queries // - Fee market module queries
type QueryClient struct { type QueryClient struct {
tx.ServiceClient tx.ServiceClient
evmtypes.QueryClient evmtypes.QueryClient

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

@ -250,8 +250,8 @@ func (s *StateDB) createObject(addr common.Address) (newobj, prev *stateObject)
// CreateAccount is called during the EVM CREATE operation. The situation might arise that // CreateAccount is called during the EVM CREATE operation. The situation might arise that
// a contract does the following: // a contract does the following:
// //
// 1. sends funds to sha(account ++ (nonce + 1)) // 1. sends funds to sha(account ++ (nonce + 1))
// 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1) // 2. tx_create(sha(account ++ nonce)) (note that this gets the address of 1)
// //
// Carrying over the balance ensures that Ether doesn't disappear. // Carrying over the balance ensures that Ether doesn't disappear.
func (s *StateDB) CreateAccount(addr common.Address) { func (s *StateDB) CreateAccount(addr common.Address) {

View File

@ -89,7 +89,7 @@ func (log *Log) ToEthereum() *ethtypes.Log {
} }
func NewLogsFromEth(ethlogs []*ethtypes.Log) []*Log { func NewLogsFromEth(ethlogs []*ethtypes.Log) []*Log {
var logs []*Log // nolint: prealloc var logs []*Log //nolint: prealloc
for _, ethlog := range ethlogs { for _, ethlog := range ethlogs {
logs = append(logs, NewLogFromEth(ethlog)) logs = append(logs, NewLogFromEth(ethlog))
} }
@ -99,7 +99,7 @@ func NewLogsFromEth(ethlogs []*ethtypes.Log) []*Log {
// LogsToEthereum casts the Ethermint Logs to a slice of Ethereum Logs. // LogsToEthereum casts the Ethermint Logs to a slice of Ethereum Logs.
func LogsToEthereum(logs []*Log) []*ethtypes.Log { func LogsToEthereum(logs []*Log) []*ethtypes.Log {
var ethLogs []*ethtypes.Log // nolint: prealloc var ethLogs []*ethtypes.Log //nolint: prealloc
for i := range logs { for i := range logs {
ethLogs = append(ethLogs, logs[i].ToEthereum()) ethLogs = append(ethLogs, logs[i].ToEthereum())
} }

View File

@ -71,8 +71,9 @@ func NewTxDataFromTx(tx *ethtypes.Transaction) (TxData, error) {
// //
// CONTRACT: v value is either: // CONTRACT: v value is either:
// //
// - {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 {

View File

@ -19,7 +19,7 @@ const (
// prefix bytes for the feemarket persistent store // prefix bytes for the feemarket persistent store
const ( const (
prefixBlockGasWanted = iota + 1 prefixBlockGasWanted = iota + 1
deprecatedPrefixBaseFee // nolint deprecatedPrefixBaseFee //nolint
) )
const ( const (