diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d040e66f0..b0544027cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ * [#1600](https://github.com/crypto-org-chain/cronos/pull/1600) Update ethermint to avoid unnecessary block result in header related api call. * [#1606](https://github.com/crypto-org-chain/cronos/pull/1606) Fix pebbledb support. * [#1610](https://github.com/crypto-org-chain/cronos/pull/1610) Sync e2ee module with v1.3.x branch. +* [#1612](https://github.com/crypto-org-chain/cronos/pull/1612) Support ibc channel upgrade related methods. ### Bug Fixes diff --git a/integration_tests/cosmoscli.py b/integration_tests/cosmoscli.py index 8d4ad25f1c..a2f23fa5ef 100644 --- a/integration_tests/cosmoscli.py +++ b/integration_tests/cosmoscli.py @@ -14,7 +14,7 @@ from dateutil.parser import isoparse from pystarport.utils import build_cli_args_safe, format_doc_string, interact -from .utils import get_sync_info +from .utils import CRONOS_ADDRESS_PREFIX, get_sync_info # the default initial base fee used by integration tests DEFAULT_GAS_PRICE = "100000000000basetcro" @@ -34,14 +34,14 @@ class ModuleAccount(enum.Enum): @format_doc_string( options=",".join(v.value for v in ModuleAccount.__members__.values()) ) -def module_address(name): +def module_address(name, prefix=CRONOS_ADDRESS_PREFIX): """ get address of module accounts :param name: name of module account, values: {options} """ data = hashlib.sha256(ModuleAccount(name).value.encode()).digest()[:20] - return bech32.bech32_encode("cro", bech32.convertbits(data, 8, 5)) + return bech32.bech32_encode(prefix, bech32.convertbits(data, 8, 5)) class ChainCommand: @@ -1238,6 +1238,27 @@ def ibc_recover_client(self, proposal, **kwargs): rsp = self.event_query_tx_for(rsp["txhash"]) return rsp + def ibc_upgrade_channels(self, version, from_addr, **kwargs): + kwargs.setdefault("gas_prices", DEFAULT_GAS_PRICE) + kwargs.setdefault("gas", 600000) + return json.loads( + self.raw( + "tx", + "ibc", + "channel", + "upgrade-channels", + json.dumps(version), + "-y", + "--json", + from_=from_addr, + keyring_backend="test", + chain_id=self.chain_id, + home=self.data_dir, + stderr=subprocess.DEVNULL, + **kwargs, + ) + ) + def submit_gov_proposal(self, proposal, **kwargs): default_kwargs = self.get_default_kwargs() kwargs.setdefault("broadcast_mode", "sync") @@ -1428,6 +1449,23 @@ def ibc_query_channels(self, connid, **kwargs): ) ) + def ibc_query_channel(self, port_id, channel_id, **kwargs): + default_kwargs = { + "node": self.node_rpc, + "output": "json", + } + return json.loads( + self.raw( + "q", + "ibc", + "channel", + "end", + port_id, + channel_id, + **(default_kwargs | kwargs), + ) + ) + def ibc_query_ack(self, port_id, channel_id, packet_seq, **kwargs): default_kwargs = { "node": self.node_rpc, diff --git a/integration_tests/ibc_utils.py b/integration_tests/ibc_utils.py index 39ab974ca4..bbc0579cf1 100644 --- a/integration_tests/ibc_utils.py +++ b/integration_tests/ibc_utils.py @@ -201,18 +201,7 @@ def prepare_network( call_rly_cmd(path, connection_only, version) if incentivized: - # register fee payee - src_chain = cronos.cosmos_cli() - dst_chain = chainmain.cosmos_cli() - rsp = dst_chain.register_counterparty_payee( - "transfer", - "channel-0", - dst_chain.address("relayer"), - src_chain.address("signer1"), - from_="relayer", - fees="100000000basecro", - ) - assert rsp["code"] == 0, rsp["raw_log"] + register_fee_payee(cronos.cosmos_cli(), chainmain.cosmos_cli()) port = None if is_relay: @@ -226,6 +215,18 @@ def prepare_network( wait_for_port(port) +def register_fee_payee(src_chain, dst_chain): + rsp = dst_chain.register_counterparty_payee( + "transfer", + "channel-0", + dst_chain.address("relayer"), + src_chain.address("signer1"), + from_="relayer", + fees="100000000basecro", + ) + assert rsp["code"] == 0, rsp["raw_log"] + + def assert_ready(ibc): # wait for hermes output = subprocess.getoutput( @@ -772,7 +773,7 @@ def register_acc(cli, connid, ordering=ChannelOrder.ORDERED.value, signer="signe version=v, ordering=ordering, ) - _, channel_id = assert_channel_open_init(rsp) + port_id, channel_id = assert_channel_open_init(rsp) wait_for_check_channel_ready(cli, connid, channel_id) print("query ica account") @@ -781,7 +782,7 @@ def register_acc(cli, connid, ordering=ChannelOrder.ORDERED.value, signer="signe cli.address(signer), )["address"] print("ica address", ica_address, "channel_id", channel_id) - return ica_address, channel_id + return ica_address, port_id, channel_id def funds_ica(cli, adr, signer="signer2"): diff --git a/integration_tests/test_ibc.py b/integration_tests/test_ibc.py index c0cfd2590e..56edf919c3 100644 --- a/integration_tests/test_ibc.py +++ b/integration_tests/test_ibc.py @@ -1,5 +1,8 @@ +import json + import pytest +from .cosmoscli import module_address from .ibc_utils import ( RATIO, assert_ready, @@ -10,10 +13,13 @@ ibc_incentivized_transfer, ibc_transfer, prepare_network, + register_fee_payee, + wait_for_check_channel_ready, ) from .utils import ( ADDRS, CONTRACTS, + approve_proposal, deploy_contract, parse_events_rpc, send_transaction, @@ -58,10 +64,39 @@ def test_ibc_transfer(ibc): assert not dup, f"duplicate {dup} in {event['type']}" -def test_ibc_incentivized_transfer(ibc): +def test_ibc_incentivized_transfer(ibc, tmp_path): if not ibc.incentivized: - # this test case only works for incentivized channel. - return + # upgrade to incentivized + src_chain = ibc.cronos.cosmos_cli() + dst_chain = ibc.chainmain.cosmos_cli() + version = {"fee_version": "ics29-1", "app_version": "ics20-1"} + community = "community" + authority = module_address("gov") + connid = "connection-0" + channel_id = "channel-0" + deposit = "1basetcro" + proposal_src = src_chain.ibc_upgrade_channels( + version, + community, + deposit=deposit, + title="channel-upgrade-title", + summary="summary", + port_pattern="transfer", + channel_ids=channel_id, + ) + proposal_src["deposit"] = deposit + proposal_src["proposer"] = authority + proposal_src["messages"][0]["signer"] = authority + proposal = tmp_path / "proposal.json" + proposal.write_text(json.dumps(proposal_src)) + rsp = src_chain.submit_gov_proposal(proposal, from_=community) + assert rsp["code"] == 0, rsp["raw_log"] + approve_proposal(ibc.cronos, rsp["events"]) + wait_for_check_channel_ready( + src_chain, connid, channel_id, "STATE_FLUSHCOMPLETE" + ) + wait_for_check_channel_ready(src_chain, connid, channel_id) + register_fee_payee(src_chain, dst_chain) ibc_incentivized_transfer(ibc) diff --git a/integration_tests/test_ica.py b/integration_tests/test_ica.py index 4a6e7f1970..5c58cfeea3 100644 --- a/integration_tests/test_ica.py +++ b/integration_tests/test_ica.py @@ -3,6 +3,7 @@ import pytest from pystarport import cluster +from .cosmoscli import module_address from .ibc_utils import ( ChannelOrder, Status, @@ -17,7 +18,7 @@ wait_for_check_tx, wait_for_status_change, ) -from .utils import CONTRACTS, wait_for_fn +from .utils import CONTRACTS, approve_proposal, wait_for_fn pytestmark = pytest.mark.ica @@ -39,12 +40,12 @@ def ibc(request, tmp_path_factory): @pytest.mark.parametrize( "order", [ChannelOrder.ORDERED.value, ChannelOrder.UNORDERED.value] ) -def test_ica(ibc, order): +def test_ica(ibc, order, tmp_path): signer = "signer2" if order == ChannelOrder.ORDERED.value else "community" connid = "connection-0" cli_host = ibc.chainmain.cosmos_cli() cli_controller = ibc.cronos.cosmos_cli() - ica_address, channel_id = register_acc( + ica_address, _, channel_id = register_acc( cli_controller, connid, ordering=order, signer=signer ) balance = funds_ica(cli_host, ica_address, signer=signer) @@ -118,9 +119,45 @@ def submit_msgs(msg_num, timeout_in_s=no_timeout, gas="200000"): else: wait_for_check_channel_ready(cli_controller, connid, channel_id, "STATE_CLOSED") # reopen ica account after channel get closed - ica_address2, channel_id2 = register_acc(cli_controller, connid) + ica_address2, port_id2, channel_id2 = register_acc(cli_controller, connid) assert ica_address2 == ica_address, ica_address2 assert channel_id2 != channel_id, channel_id2 + # upgrade to unordered channel + channel = cli_controller.ibc_query_channel(port_id2, channel_id2) + version_data = json.loads(channel["channel"]["version"]) + community = "community" + authority = module_address("gov") + deposit = "1basetcro" + proposal_src = cli_controller.ibc_upgrade_channels( + json.loads(version_data["app_version"]), + community, + deposit=deposit, + title="channel-upgrade-title", + summary="summary", + port_pattern=port_id2, + channel_ids=channel_id2, + ) + proposal_src["deposit"] = deposit + proposal_src["proposer"] = authority + proposal_src["messages"][0]["signer"] = authority + proposal_src["messages"][0]["fields"]["ordering"] = ChannelOrder.UNORDERED.value + proposal = tmp_path / "proposal.json" + proposal.write_text(json.dumps(proposal_src)) + rsp = cli_controller.submit_gov_proposal(proposal, from_=community) + assert rsp["code"] == 0, rsp["raw_log"] + approve_proposal(ibc.cronos, rsp["events"]) + wait_for_check_channel_ready( + cli_controller, connid, channel_id2, "STATE_FLUSHCOMPLETE" + ) + wait_for_check_channel_ready(cli_controller, connid, channel_id2) + # submit large txs to trigger close channel with small timeout for order channel + msg_num = 140 + submit_msgs(msg_num, 0.005, "600000") + assert cli_host.balance(ica_address, denom=denom) == balance + with pytest.raises(AssertionError) as exc: + register_acc(cli_controller, connid) + assert "existing active channel" in str(exc.value) + # submit normal txs should work msg_num = 2 submit_msgs(msg_num) diff --git a/integration_tests/test_ica_incentivized.py b/integration_tests/test_ica_incentivized.py index faea3222c0..4aa53e575a 100644 --- a/integration_tests/test_ica_incentivized.py +++ b/integration_tests/test_ica_incentivized.py @@ -26,7 +26,7 @@ def test_incentivized(ibc): connid = "connection-0" cli_host = ibc.chainmain.cosmos_cli() cli_controller = ibc.cronos.cosmos_cli() - ica_address, channel_id = register_acc(cli_controller, connid) + ica_address, _, channel_id = register_acc(cli_controller, connid) relayer = cli_controller.address("signer1") balance = funds_ica(cli_host, ica_address) ibc.cronos.supervisorctl("stop", "relayer-demo") diff --git a/x/cronos/middleware/conversion_middleware.go b/x/cronos/middleware/conversion_middleware.go index 22deb4c7d6..12dd7b23be 100644 --- a/x/cronos/middleware/conversion_middleware.go +++ b/x/cronos/middleware/conversion_middleware.go @@ -13,6 +13,8 @@ import ( cronoskeeper "github.com/crypto-org-chain/cronos/v2/x/cronos/keeper" ) +var _ porttypes.UpgradableModule = (*IBCConversionModule)(nil) + // IBCConversionModule implements the ICS26 interface. type IBCConversionModule struct { app porttypes.IBCModule @@ -228,3 +230,46 @@ func (im IBCConversionModule) getIbcDenomFromPacketAndData( denomTrace := transferTypes.ParseDenomTrace(prefixedDenom) return denomTrace.IBCDenom() } + +// OnChanUpgradeInit implements the IBCModule interface +func (im IBCConversionModule) OnChanUpgradeInit( + ctx sdk.Context, + portID string, + channelID string, + proposedOrder channeltypes.Order, + proposedConnectionHops []string, + proposedVersion string, +) (string, error) { + cbs, ok := im.app.(porttypes.UpgradableModule) + if !ok { + return "", errors.Wrap(porttypes.ErrInvalidRoute, "upgrade route not found to module in application callstack") + } + return cbs.OnChanUpgradeInit(ctx, portID, channelID, proposedOrder, proposedConnectionHops, proposedVersion) +} + +// OnChanUpgradeAck implements the IBCModule interface +func (im IBCConversionModule) OnChanUpgradeAck(ctx sdk.Context, portID, channelID, counterpartyVersion string) error { + cbs, ok := im.app.(porttypes.UpgradableModule) + if !ok { + return errors.Wrap(porttypes.ErrInvalidRoute, "upgrade route not found to module in application callstack") + } + return cbs.OnChanUpgradeAck(ctx, portID, channelID, counterpartyVersion) +} + +// OnChanUpgradeOpen implements the IBCModule interface +func (im IBCConversionModule) OnChanUpgradeOpen(ctx sdk.Context, portID, channelID string, proposedOrder channeltypes.Order, proposedConnectionHops []string, proposedVersion string) { + cbs, ok := im.app.(porttypes.UpgradableModule) + if !ok { + panic(errors.Wrap(porttypes.ErrInvalidRoute, "upgrade route not found to module in application callstack")) + } + cbs.OnChanUpgradeOpen(ctx, portID, channelID, proposedOrder, proposedConnectionHops, proposedVersion) +} + +// OnChanUpgradeTry implement s the IBCModule interface +func (im IBCConversionModule) OnChanUpgradeTry(ctx sdk.Context, portID, channelID string, proposedOrder channeltypes.Order, proposedConnectionHops []string, counterpartyVersion string) (string, error) { + cbs, ok := im.app.(porttypes.UpgradableModule) + if !ok { + return "", errors.Wrap(porttypes.ErrInvalidRoute, "upgrade route not found to module in application callstack") + } + return cbs.OnChanUpgradeTry(ctx, portID, channelID, proposedOrder, proposedConnectionHops, counterpartyVersion) +}