Skip to content
This repository has been archived by the owner on Apr 4, 2024. It is now read-only.

Prune Node Integration Tests #1212

Merged
merged 22 commits into from
Aug 8, 2022
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion rpc/backend/evm_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -871,7 +871,7 @@ func (b *Backend) BaseFee(blockRes *tmrpctypes.ResultBlockResults) (*big.Int, er
}

if res.BaseFee == nil {
ramacarlucho marked this conversation as resolved.
Show resolved Hide resolved
return nil, nil
return nil, errors.New("pruned node")
}

return res.BaseFee.BigInt(), nil
Expand Down
5 changes: 5 additions & 0 deletions rpc/namespaces/ethereum/eth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,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.GTE(sdk.NewIntFromUint64(0)) {
facs95 marked this conversation as resolved.
Show resolved Hide resolved
return nil, errors.New("pruned node, cant get balance of pruned states")
fedekunze marked this conversation as resolved.
Show resolved Hide resolved
}

return (*hexutil.Big)(val.BigInt()), nil
}

Expand Down
2 changes: 1 addition & 1 deletion tests/integration_tests/configs/default.jsonnet
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
dotenv: '../../../scripts/.env',
'ethermintd_777-1': {
'ethermint_9000-1': {
cmd: 'ethermintd',
'start-flags': '--trace',
'app-config': {
Expand Down
21 changes: 21 additions & 0 deletions tests/integration_tests/configs/pruned_node.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
local config = import 'default.jsonnet';

config {
'ethermint_9000-1'+: {
'app-config'+: {
pruning: 'everything',
'state-sync'+: {
'snapshot-interval': 0,
},
},
genesis+: {
app_state+: {
feemarket+: {
params+: {
min_gas_multiplier: '0',
ramacarlucho marked this conversation as resolved.
Show resolved Hide resolved
ramacarlucho marked this conversation as resolved.
Show resolved Hide resolved
},
},
},
},
},
}
20 changes: 16 additions & 4 deletions tests/integration_tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import pytest

from .network import setup_ethermint, setup_geth
from pathlib import Path
from .network import setup_ethermint, setup_geth, setup_custom_ethermint

@pytest.fixture(scope="session")
def ethermint(tmp_path_factory):
Expand All @@ -13,9 +13,19 @@ def geth(tmp_path_factory):
path = tmp_path_factory.mktemp("geth")
yield from setup_geth(path, 8545)

@pytest.fixture(scope="session")
def pruned(request, tmp_path_factory):
"""start-ethermint
params: enable_auto_deployment
"""
yield from setup_custom_ethermint(
tmp_path_factory.mktemp("pruned"),
26900,
Path(__file__).parent / "configs/pruned_node.jsonnet",
)

@pytest.fixture(scope="session", params=["ethermint", "geth", "ethermint-ws"])
def cluster(request, ethermint, geth):
@pytest.fixture(scope="session", params=["ethermint", "geth", "pruned", "ethermint-ws"])
def cluster(request, ethermint, geth, pruned):
"""
run on both ethermint and geth
"""
Expand All @@ -24,6 +34,8 @@ def cluster(request, ethermint, geth):
yield ethermint
elif provider == "geth":
yield geth
elif provider == "pruned":
yield pruned
facs95 marked this conversation as resolved.
Show resolved Hide resolved
elif provider == "ethermint-ws":
ethermint_ws = ethermint.copy()
ethermint_ws.use_websocket()
Expand Down
114 changes: 114 additions & 0 deletions tests/integration_tests/test_pruned_node.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
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,
)

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)

#txreceipt = w3.eth.wait_for_transaction_receipt(txhash.hex())
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
facs95 marked this conversation as resolved.
Show resolved Hide resolved
facs95 marked this conversation as resolved.
Show resolved Hide resolved
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)])
print(pruned_res)
assert 'error' in pruned_res
assert pruned_res['error']['message']=='pruned node, cant get balance of pruned states'

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)

# Pruned node doesnt keep baseFeePerGas for pruned blocks
assert "baseFeePerGas" not 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": 51503,
"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]

print(
w3.eth.get_logs(
{"fromBlock": tx_receipt.blockNumber, "toBlock": tx_receipt.blockNumber}
)
)
ramacarlucho marked this conversation as resolved.
Show resolved Hide resolved
73 changes: 73 additions & 0 deletions tests/integration_tests/utils.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,42 @@
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/"
/ filename
/ (name + ".json")
)


CONTRACTS = {
**{
name: contract_path(name, filename) for name, filename in TEST_CONTRACTS.items()
},
}

def wait_for_port(port, host="127.0.0.1", timeout=40.0):
start_time = time.perf_counter()
Expand All @@ -14,3 +51,39 @@ def wait_for_port(port, host="127.0.0.1", timeout=40.0):
"Waited too long for the port {} on host {} to start accepting "
"connections.".format(port, host)
) 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 sign_transaction(w3, tx, key=KEYS["validator"]):
"fill default fields and sign"
acct = Account.from_key(key)
tx["from"] = acct.address
tx = fill_transaction_defaults(w3, tx)
tx = fill_nonce(w3, tx)
return acct.sign_transaction(tx)


def send_transaction(w3, tx, key=KEYS["validator"]):
signed = sign_transaction(w3, tx, key)
txhash = w3.eth.send_raw_transaction(signed.rawTransaction)
return w3.eth.wait_for_transaction_receipt(txhash)
4 changes: 4 additions & 0 deletions x/evm/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,10 @@ func (k *Keeper) GetNonce(ctx sdk.Context, addr common.Address) uint64 {
func (k *Keeper) GetBalance(ctx sdk.Context, addr common.Address) *big.Int {
cosmosAddr := sdk.AccAddress(addr.Bytes())
params := k.GetParams(ctx)
// if node is pruned, params is empty. Return invalid value
if params.EvmDenom == "" {
ramacarlucho marked this conversation as resolved.
Show resolved Hide resolved
return big.NewInt(-1)
}
coin := k.bankKeeper.GetBalance(ctx, cosmosAddr, params.EvmDenom)
return coin.Amount.BigInt()
}
Expand Down