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:
parent
7331cd2065
commit
ccbaf1fe05
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
*/
|
*/
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
9
tests/integration_tests/.isort.cfg
Normal file
9
tests/integration_tests/.isort.cfg
Normal 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
|
12
tests/integration_tests/configs/pruned_node.jsonnet
Normal file
12
tests/integration_tests/configs/pruned_node.jsonnet
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
local config = import 'default.jsonnet';
|
||||||
|
|
||||||
|
config {
|
||||||
|
'ethermint_9000-1'+: {
|
||||||
|
'app-config'+: {
|
||||||
|
pruning: 'everything',
|
||||||
|
'state-sync'+: {
|
||||||
|
'snapshot-interval': 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
142
tests/integration_tests/test_pruned_node.py
Normal file
142
tests/integration_tests/test_pruned_node.py
Normal 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"
|
||||||
|
)
|
@ -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))
|
||||||
|
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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) {
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user