forked from cerc-io/laconicd-deprecated
fix(rpc): fix gRPC query failure on legacy blocks (#1354)
* Problem: grpc query fail on legacy blocks `BaseFee` and `EthCall`. Solution: - since grpc query handlers are used for all versions of the blocks, it need to be compatible with legacy formats. debug fix basefee fetch Revert "debug" This reverts commit 50ebaf697fc06b0d6e26abd8de8f89717e8a219d. update gomod2nix Update CHANGELOG.md debug fix panic Revert "debug" This reverts commit e08af04b0776bd390c42706cc9ec978e00bcb3bb. * add upgrade integration test * Update tests/integration_tests/configs/upgrade-test-package.nix Co-authored-by: Federico Kunze Küllmer <31522760+fedekunze@users.noreply.github.com>
This commit is contained in:
co-authored by
Federico Kunze Küllmer
parent
d15007b4d9
commit
35850e620d
@@ -0,0 +1,17 @@
|
||||
local config = import 'default.jsonnet';
|
||||
|
||||
config {
|
||||
'ethermint_9000-1'+: {
|
||||
genesis+: {
|
||||
app_state+: {
|
||||
feemarket+: {
|
||||
params+: {
|
||||
no_base_fee: false,
|
||||
base_fee:: super.base_fee,
|
||||
initial_base_fee: super.base_fee,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
let
|
||||
pkgs = import ../../../nix { };
|
||||
fetchEthermint = rev: builtins.fetchTarball "https://github.com/evmos/ethermint/archive/${rev}.tar.gz";
|
||||
released = pkgs.buildGo118Module rec {
|
||||
name = "ethermintd";
|
||||
# the commit before https://github.com/evmos/ethermint/pull/943
|
||||
src = fetchEthermint "f21592ebfe74da7590eb42ed926dae970b2a9a3f";
|
||||
subPackages = [ "cmd/ethermintd" ];
|
||||
vendorSha256 = "sha256-ABm5t6R/u2S6pThGrgdsqe8n3fH5tIWw7a57kxJPbYw=";
|
||||
doCheck = false;
|
||||
};
|
||||
current = pkgs.callPackage ../../../. { };
|
||||
in
|
||||
pkgs.linkFarm "upgrade-test-package" [
|
||||
{ name = "genesis"; path = released; }
|
||||
{ name = "integration-test-upgrade"; path = current; }
|
||||
]
|
||||
@@ -829,3 +829,6 @@ class CosmosCLI:
|
||||
|
||||
def rollback(self):
|
||||
self.raw("rollback", home=self.data_dir)
|
||||
|
||||
def migrate_keystore(self):
|
||||
return self.raw("keys", "migrate", home=self.data_dir)
|
||||
|
||||
@@ -11,14 +11,17 @@ from web3.middleware import geth_poa_middleware
|
||||
from .cosmoscli import CosmosCLI
|
||||
from .utils import wait_for_port
|
||||
|
||||
DEFAULT_CHAIN_BINARY = "ethermintd"
|
||||
|
||||
|
||||
class Ethermint:
|
||||
def __init__(self, base_dir):
|
||||
def __init__(self, base_dir, chain_binary=DEFAULT_CHAIN_BINARY):
|
||||
self._w3 = None
|
||||
self.base_dir = base_dir
|
||||
self.config = json.loads((base_dir / "config.json").read_text())
|
||||
self.enable_auto_deployment = False
|
||||
self._use_websockets = False
|
||||
self.chain_binary = chain_binary
|
||||
|
||||
def copy(self):
|
||||
return Ethermint(self.base_dir)
|
||||
@@ -55,7 +58,9 @@ class Ethermint:
|
||||
self._use_websockets = use
|
||||
|
||||
def cosmos_cli(self, i=0):
|
||||
return CosmosCLI(self.base_dir / f"node{i}", self.node_rpc(i), "ethermintd")
|
||||
return CosmosCLI(
|
||||
self.base_dir / f"node{i}", self.node_rpc(i), self.chain_binary
|
||||
)
|
||||
|
||||
|
||||
class Geth:
|
||||
@@ -124,7 +129,9 @@ def setup_custom_ethermint(
|
||||
if wait_port:
|
||||
wait_for_port(ports.evmrpc_port(base_port))
|
||||
wait_for_port(ports.evmrpc_ws_port(base_port))
|
||||
yield Ethermint(path / "ethermint_9000-1")
|
||||
yield Ethermint(
|
||||
path / "ethermint_9000-1", chain_binary=chain_binary or DEFAULT_CHAIN_BINARY
|
||||
)
|
||||
finally:
|
||||
os.killpg(os.getpgid(proc.pid), signal.SIGTERM)
|
||||
proc.wait()
|
||||
|
||||
@@ -0,0 +1,173 @@
|
||||
import configparser
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from dateutil.parser import isoparse
|
||||
from pystarport import ports
|
||||
from pystarport.cluster import SUPERVISOR_CONFIG_FILE
|
||||
|
||||
from .network import Ethermint, setup_custom_ethermint
|
||||
from .utils import (
|
||||
ADDRS,
|
||||
CONTRACTS,
|
||||
deploy_contract,
|
||||
parse_events,
|
||||
send_transaction,
|
||||
wait_for_block,
|
||||
wait_for_block_time,
|
||||
wait_for_port,
|
||||
)
|
||||
|
||||
|
||||
def init_cosmovisor(home):
|
||||
"""
|
||||
build and setup cosmovisor directory structure in each node's home directory
|
||||
"""
|
||||
cosmovisor = home / "cosmovisor"
|
||||
cosmovisor.mkdir()
|
||||
(cosmovisor / "upgrades").symlink_to("../../../upgrades")
|
||||
(cosmovisor / "genesis").symlink_to("./upgrades/genesis")
|
||||
|
||||
|
||||
def post_init(path, base_port, config):
|
||||
"""
|
||||
prepare cosmovisor for each node
|
||||
"""
|
||||
chain_id = "ethermint_9000-1"
|
||||
cfg = json.loads((path / chain_id / "config.json").read_text())
|
||||
for i, _ in enumerate(cfg["validators"]):
|
||||
home = path / chain_id / f"node{i}"
|
||||
init_cosmovisor(home)
|
||||
|
||||
# patch supervisord ini config
|
||||
ini_path = path / chain_id / SUPERVISOR_CONFIG_FILE
|
||||
ini = configparser.RawConfigParser()
|
||||
ini.read(ini_path)
|
||||
reg = re.compile(rf"^program:{chain_id}-node(\d+)")
|
||||
for section in ini.sections():
|
||||
m = reg.match(section)
|
||||
if m:
|
||||
i = m.group(1)
|
||||
ini[section].update(
|
||||
{
|
||||
"command": f"cosmovisor start --home %(here)s/node{i}",
|
||||
"environment": (
|
||||
f"DAEMON_NAME=ethermintd,DAEMON_HOME=%(here)s/node{i}"
|
||||
),
|
||||
}
|
||||
)
|
||||
with ini_path.open("w") as fp:
|
||||
ini.write(fp)
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def custom_ethermint(tmp_path_factory):
|
||||
path = tmp_path_factory.mktemp("upgrade")
|
||||
cmd = [
|
||||
"nix-build",
|
||||
Path(__file__).parent / "configs/upgrade-test-package.nix",
|
||||
"-o",
|
||||
path / "upgrades",
|
||||
]
|
||||
print(*cmd)
|
||||
subprocess.run(cmd, check=True)
|
||||
# init with genesis binary
|
||||
yield from setup_custom_ethermint(
|
||||
path,
|
||||
26100,
|
||||
Path(__file__).parent / "configs/cosmovisor.jsonnet",
|
||||
post_init=post_init,
|
||||
chain_binary=str(path / "upgrades/genesis/bin/ethermintd"),
|
||||
)
|
||||
|
||||
|
||||
def test_cosmovisor_upgrade(custom_ethermint: Ethermint):
|
||||
"""
|
||||
- propose an upgrade and pass it
|
||||
- wait for it to happen
|
||||
- it should work transparently
|
||||
- check that queries on legacy blocks still works after upgrade.
|
||||
"""
|
||||
cli = custom_ethermint.cosmos_cli()
|
||||
height = cli.block_height()
|
||||
target_height = height + 5
|
||||
print("upgrade height", target_height)
|
||||
|
||||
w3 = custom_ethermint.w3
|
||||
contract = deploy_contract(w3, CONTRACTS["TestERC20A"])
|
||||
old_height = w3.eth.block_number
|
||||
old_balance = w3.eth.get_balance(ADDRS["validator"], block_identifier=old_height)
|
||||
old_base_fee = w3.eth.get_block(old_height).baseFeePerGas
|
||||
old_erc20_balance = contract.caller.balanceOf(ADDRS["validator"])
|
||||
print("old values", old_height, old_balance, old_base_fee)
|
||||
|
||||
plan_name = "integration-test-upgrade"
|
||||
rsp = cli.gov_propose(
|
||||
"community",
|
||||
"software-upgrade",
|
||||
{
|
||||
"name": plan_name,
|
||||
"title": "upgrade test",
|
||||
"description": "ditto",
|
||||
"upgrade-height": target_height,
|
||||
"deposit": "10000aphoton",
|
||||
},
|
||||
)
|
||||
assert rsp["code"] == 0, rsp["raw_log"]
|
||||
|
||||
# get proposal_id
|
||||
ev = parse_events(rsp["logs"])["submit_proposal"]
|
||||
assert ev["proposal_type"] == "SoftwareUpgrade", rsp
|
||||
proposal_id = ev["proposal_id"]
|
||||
|
||||
rsp = cli.gov_vote("validator", proposal_id, "yes")
|
||||
assert rsp["code"] == 0, rsp["raw_log"]
|
||||
# rsp = custom_ethermint.cosmos_cli(1).gov_vote("validator", proposal_id, "yes")
|
||||
# assert rsp["code"] == 0, rsp["raw_log"]
|
||||
|
||||
proposal = cli.query_proposal(proposal_id)
|
||||
wait_for_block_time(cli, isoparse(proposal["voting_end_time"]))
|
||||
proposal = cli.query_proposal(proposal_id)
|
||||
assert proposal["status"] == "PROPOSAL_STATUS_PASSED", proposal
|
||||
|
||||
# update cli chain binary
|
||||
custom_ethermint.chain_binary = (
|
||||
Path(custom_ethermint.chain_binary).parent.parent.parent
|
||||
/ f"{plan_name}/bin/ethermintd"
|
||||
)
|
||||
cli = custom_ethermint.cosmos_cli()
|
||||
|
||||
# block should pass the target height
|
||||
wait_for_block(cli, target_height + 1, timeout=480)
|
||||
wait_for_port(ports.rpc_port(custom_ethermint.base_port(0)))
|
||||
|
||||
# test migrate keystore
|
||||
cli.migrate_keystore()
|
||||
|
||||
# check basic tx works after upgrade
|
||||
wait_for_port(ports.evmrpc_port(custom_ethermint.base_port(0)))
|
||||
|
||||
receipt = send_transaction(
|
||||
w3,
|
||||
{
|
||||
"to": ADDRS["community"],
|
||||
"value": 1000,
|
||||
"maxFeePerGas": 1000000000000,
|
||||
"maxPriorityFeePerGas": 10000,
|
||||
},
|
||||
)
|
||||
assert receipt.status == 1
|
||||
|
||||
# check json-rpc query on older blocks works
|
||||
assert old_balance == w3.eth.get_balance(
|
||||
ADDRS["validator"], block_identifier=old_height
|
||||
)
|
||||
assert old_base_fee == w3.eth.get_block(old_height).baseFeePerGas
|
||||
|
||||
# check eth_call on older blocks works
|
||||
assert old_erc20_balance == contract.caller(
|
||||
block_identifier=target_height - 2
|
||||
).balanceOf(ADDRS["validator"])
|
||||
@@ -7,6 +7,7 @@ import time
|
||||
from pathlib import Path
|
||||
|
||||
import bech32
|
||||
from dateutil.parser import isoparse
|
||||
from dotenv import load_dotenv
|
||||
from eth_account import Account
|
||||
from hexbytes import HexBytes
|
||||
@@ -109,6 +110,16 @@ def w3_wait_for_block(w3, height, timeout=240):
|
||||
raise TimeoutError(f"wait for block {height} timeout")
|
||||
|
||||
|
||||
def wait_for_block_time(cli, t):
|
||||
print("wait for block time", t)
|
||||
while True:
|
||||
now = isoparse((cli.status())["SyncInfo"]["latest_block_time"])
|
||||
print("block time now:", now)
|
||||
if now >= t:
|
||||
break
|
||||
time.sleep(0.5)
|
||||
|
||||
|
||||
def deploy_contract(w3, jsonfile, args=(), key=KEYS["validator"]):
|
||||
"""
|
||||
deploy contract and return the deployed contract instance
|
||||
@@ -160,3 +171,10 @@ def supervisorctl(inipath, *args):
|
||||
(sys.executable, "-msupervisor.supervisorctl", "-c", inipath, *args),
|
||||
check=True,
|
||||
)
|
||||
|
||||
|
||||
def parse_events(logs):
|
||||
return {
|
||||
ev["type"]: {attr["key"]: attr["value"] for attr in ev["attributes"]}
|
||||
for ev in logs[0]["events"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user