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
- uses: golangci/golangci-lint-action@v3
with:
version: latest
version: v1.48.0
args: --timeout 10m
github-token: ${{ secrets.github_token }}
# 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).
During CheckTx, the transaction is passed through a series of

View File

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

View File

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

View File

@ -40,7 +40,7 @@ func SetBech32Prefixes(config *sdk.Config) {
func SetBip44CoinType(config *sdk.Config) {
config.SetCoinType(ethermint.Bip44CoinType)
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.

View File

@ -22,7 +22,7 @@ import (
)
// BackendI implements the Cosmos and EVM backend.
type BackendI interface { // nolint: revive
type BackendI interface { //nolint: revive
CosmosBackend
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
// 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)
if err := a.StartCPUProfile(file); err != nil {
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.
func (e *PublicAPI) ChainId() (*hexutil.Big, error) { // nolint
func (e *PublicAPI) ChainId() (*hexutil.Big, error) { //nolint
e.logger.Debug("eth_chainId")
// if current block is at or past the EIP-155 replay-protection fork block, return chainID from config
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")
}
// 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
}

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
// system. Since the bloombits are not positional, nil topics are permitted,
// which get flattened into a nil byte slice.
var filtersBz [][][]byte // nolint: prealloc
var filtersBz [][][]byte //nolint: prealloc
if len(addresses) > 0 {
filter := make([][]byte, len(addresses))
for i, address := range addresses {

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 socket
import time
from pathlib import Path
from dotenv import load_dotenv
from eth_account import Account
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()
ACCOUNTS = {
@ -32,6 +66,29 @@ def wait_for_port(port, host="127.0.0.1", timeout=40.0):
) 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):
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
func (k *Keeper) GetBalance(ctx sdk.Context, addr common.Address) *big.Int {
cosmosAddr := sdk.AccAddress(addr.Bytes())
params := k.GetParams(ctx)
coin := k.bankKeeper.GetBalance(ctx, cosmosAddr, params.EvmDenom)
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()
}

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
// 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
// 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
// and the transaction won't be committed to the store.
//
// Reverted state
// # Reverted state
//
// The snapshot and rollback are supported by the `statedb.StateDB`.
//
// Different Callers
// # Different Callers
//
// It's called in three scenarios:
// 1. `ApplyTransaction`, in the transaction processing flow.
// 2. `EthCall/EthEstimateGas` grpc query handler.
// 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,
// 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)
//
// 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.
//
// Commit parameter
// # Commit parameter
//
// 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) {

View File

@ -89,7 +89,7 @@ func (log *Log) ToEthereum() *ethtypes.Log {
}
func NewLogsFromEth(ethlogs []*ethtypes.Log) []*Log {
var logs []*Log // nolint: prealloc
var logs []*Log //nolint: prealloc
for _, ethlog := range ethlogs {
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.
func LogsToEthereum(logs []*Log) []*ethtypes.Log {
var ethLogs []*ethtypes.Log // nolint: prealloc
var ethLogs []*ethtypes.Log //nolint: prealloc
for i := range logs {
ethLogs = append(ethLogs, logs[i].ToEthereum())
}

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} + 27, otherwise
//
// Ref: https://github.com/ethereum/EIPs/blob/master/EIPS/eip-155.md
func DeriveChainID(v *big.Int) *big.Int {
if v == nil || v.Sign() < 1 {

View File

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