Skip to content

How to upgrade a client

Carlos Rodriguez edited this page Mar 4, 2024 · 6 revisions

This is a step-by-step guide of how to upgrade an IBC client. The example explained in this guide assumes the following:

  • Two chains (with chain IDs chain1 and chain2):
    • running locally with a single validator node,
    • that start from clean state,
    • with 2 accounts (1 validator, 1 wallet).
  • chain1's REST API runs on http://localhost:27001 and chain2's REST API runs on http://localhost:27011.
  • The chain binary for both chains is the simd binary of ibc-go for v5.0.0-rc1.
  • The relayer used is hermes v1.0.0.

Therefore based on your own situation, you might need to adjust the parameters or commands presented here.

Summary

We are going to upgrade the client state on chain1 with a change to the unbonding period. The light client of the counterparty chain2 will be updated with the new unbonding period.

This guide is relevant only for Tendermint chains that would break counterparty IBC Tendermint Clients.

Some other useful resources:

Step 1: change the gov parameters in genesis.json

For the purposes of this example we are going to change some of the gov parameters in the genesis.json file, so that we can complete the upgrade faster. These are the changes needed in the genesis.json of chain1:

"gov": {
  "starting_proposal_id": "1",
  "deposits": [],
  "votes": [],
  "proposals": [],
  "deposit_params": {
    "min_deposit": [
      {
        "denom": "stake",
-       "amount": "10000000"
+       "amount": "100"
      }
    ],
    "max_deposit_period": "172800s"
  },
  "voting_params": {
-   "voting_period": "172800s"
+   "voting_period": "180s"
  },
  "tally_params": {
    "quorum": "0.334000000000000000",
-   "threshold": "0.500000000000000000",
+   "threshold": "0.300000000000000000",
    "veto_threshold": "0.334000000000000000"
  }
},

Step 2: create a light client for chain1 on chain2

When we upgrade the client state of chain1, light clients for chain1 on counterparty chains will need to be updated. So we create a light client on chain2 using hermes:

> hermes create client --host-chain chain2 --reference-chain chain1

The light client creation succeeds and it is initialized with the consensus state of chain1 at height 28:

SUCCESS CreateClient(
  CreateClient(
    Attributes {
      height: Height {
        revision: 0,
        height: 29,
      },
      client_id: ClientId(
        "07-tendermint-0",
      ),
      client_type: Tendermint,
      consensus_height: Height {
        revision: 0,
        height: 28,
      },
    },
  ),
)

We can use now ibc-go's REST interface to check the existence of the client state for chain1 by simply entering http://localhost:27011/ibc/core/client/v1/client_states/07-tendermint-0 on the browser address bar (note that, in my case, the REST API for chain2 is running on localhost:27011):

{
  "client_state": {
    "@type": "/ibc.lightclients.tendermint.v1.ClientState",
    "chain_id": "chain1",
    "trust_level": {
      "numerator": "1",
      "denominator": "3"
    },
    "trusting_period": "1209600s",
    "unbonding_period": "1814400s",
    "max_clock_drift": "40s",
    "frozen_height": {
      "revision_number": "0",
      "revision_height": "0"
    },
    "latest_height": {
      "revision_number": "0",
      "revision_height": "28"
    },
    "proof_specs": [
      {
        "leaf_spec": {
          "hash": "SHA256",
          "prehash_key": "NO_HASH",
          "prehash_value": "SHA256",
          "length": "VAR_PROTO",
          "prefix": "AA=="
        },
        "inner_spec": {
          "child_order": [
            0,
            1
          ],
          "child_size": 33,
          "min_prefix_length": 4,
          "max_prefix_length": 12,
          "empty_child": null,
          "hash": "SHA256"
        },
        "max_depth": 0,
        "min_depth": 0
      },
      {
        "leaf_spec": {
          "hash": "SHA256",
          "prehash_key": "NO_HASH",
          "prehash_value": "SHA256",
          "length": "VAR_PROTO",
          "prefix": "AA=="
        },
        "inner_spec": {
          "child_order": [
            0,
            1
          ],
          "child_size": 32,
          "min_prefix_length": 1,
          "max_prefix_length": 1,
          "empty_child": null,
          "hash": "SHA256"
        },
        "max_depth": 0,
        "min_depth": 0
      }
    ],
    "upgrade_path": [
      "upgrade",
      "upgradedIBCState"
    ],
    "allow_update_after_expiry": true,
    "allow_update_after_misbehaviour": true
  },
  "proof": null,
  "proof_height": {
    "revision_number": "0",
    "revision_height": "37"
  }
}

Step 3: submit a governance proposal to upgrade client state on chain1

We need now to prepare an UpgradeProposal with an upgrade plan that contains the new IBC client state in the upgraded_client_state field. The upgrade plan must specify an upgrade height only (no upgrade time), and the client state should only include the fields common to all valid clients and zero out any client-customizable fields (such as the trusting period). Using the client state retrieved from chain2's REST API, we create the following upgraded client state with a new unbonding_period of 2419200s:

{
  "@type": "/ibc.lightclients.tendermint.v1.ClientState",
  "chain_id": "chain1",
  "unbonding_period": "2419200s",
  "latest_height": {
    "revision_number": "0",
    "revision_height": "201"
  },
  "proof_specs": [
    {
      "leaf_spec": {
        "hash": "SHA256",
        "prehash_key": "NO_HASH",
        "prehash_value": "SHA256",
        "length": "VAR_PROTO",
        "prefix": "AA=="
      },
      "inner_spec": {
        "child_order": [
          0,
          1
        ],
        "child_size": 33,
        "min_prefix_length": 4,
        "max_prefix_length": 12,
        "empty_child": null,
        "hash": "SHA256"
      },
      "max_depth": 0,
      "min_depth": 0
    },
    {
      "leaf_spec": {
        "hash": "SHA256",
        "prehash_key": "NO_HASH",
        "prehash_value": "SHA256",
        "length": "VAR_PROTO",
        "prefix": "AA=="
      },
      "inner_spec": {
        "child_order": [
          0,
          1
        ],
        "child_size": 32,
        "min_prefix_length": 1,
        "max_prefix_length": 1,
        "empty_child": null,
        "hash": "SHA256"
      },
      "max_depth": 0,
      "min_depth": 0
    }
  ],
  "upgrade_path": [
    "upgrade",
    "upgradedIBCState"
  ]
}

And we store this in a file called upgraded_client_state.json.

We use the CLI command to submit an upgrade proposal to plan the upgrade to happen at height 200:

simd tx gov submit-legacy-proposal ibc-upgrade increase-unbonding-period 200 ./upgraded_client_state.json \
--title increase-unbonding-period \
--description increase-unbonding-period \
--deposit 100stake \
--from validator \
--chain-id chain1 \
--keyring-backend test \
--home ../../gm/chain1 \
--node http://localhost:27000

This will make the chain commit to the correct upgraded (self) client state before the upgrade occurs, so that connecting chains can verify that the new upgraded client is valid by verifying a proof on the previous version of the chain. This will allow IBC connections to persist smoothly across planned chain upgrades.

And now we can check that the proposal has been accepted and it has entered the voting period:

> simd q gov proposals --node http://localhost:27000
pagination:
  next_key: null
  total: "0"
proposals:
- deposit_end_time: "2022-09-17T18:46:25.607417Z"
  final_tally_result:
    abstain_count: "0"
    no_count: "0"
    no_with_veto_count: "0"
    yes_count: "0"
  id: "1"
  messages:
  - '@type': /cosmos.gov.v1.MsgExecLegacyContent
    authority: cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn
    content:
      '@type': /ibc.core.client.v1.UpgradeProposal
      description: increase-unbonding-period
      plan:
        height: "200"
        info: ""
        name: increase-unbonding-period
        time: "0001-01-01T00:00:00Z"
        upgraded_client_state: null
      title: increase-unbonding-period
      upgraded_client_state:
        '@type': /ibc.lightclients.tendermint.v1.ClientState
        allow_update_after_expiry: false
        allow_update_after_misbehaviour: false
        chain_id: chain1
        frozen_height:
          revision_height: "0"
          revision_number: "0"
        latest_height:
          revision_height: "201"
          revision_number: "0"
        max_clock_drift: 0s
        proof_specs:
        - inner_spec:
            child_order:
            - 0
            - 1
            child_size: 33
            empty_child: null
            hash: SHA256
            max_prefix_length: 12
            min_prefix_length: 4
          leaf_spec:
            hash: SHA256
            length: VAR_PROTO
            prefix: AA==
            prehash_key: NO_HASH
            prehash_value: SHA256
          max_depth: 0
          min_depth: 0
        - inner_spec:
            child_order:
            - 0
            - 1
            child_size: 32
            empty_child: null
            hash: SHA256
            max_prefix_length: 1
            min_prefix_length: 1
          leaf_spec:
            hash: SHA256
            length: VAR_PROTO
            prefix: AA==
            prehash_key: NO_HASH
            prehash_value: SHA256
          max_depth: 0
          min_depth: 0
        trust_level:
          denominator: "0"
          numerator: "0"
        trusting_period: 0s
        unbonding_period: 2419200s
        upgrade_path:
        - upgrade
        - upgradedIBCState
  metadata: ""
  status: PROPOSAL_STATUS_VOTING_PERIOD
  submit_time: "2022-09-15T18:46:25.607417Z"
  total_deposit:
  - amount: "100"
    denom: stake
  voting_end_time: "2022-09-15T18:48:25.607417Z"
  voting_start_time: "2022-09-15T18:46:25.607417Z"

Now we vote for the proposal:

simd tx gov vote 1 yes \
--from validator \
--chain-id chain1 \
--keyring-backend test \
--home ../../gm/chain1 \
--node http://localhost:27000

And we wait for the voting period to end. Once it ends we can check that the proposal has passed (i.e. the status has changed from PROPOSAL_STATUS_VOTING_PERIOD to PROPOSAL_STATUS_PASSED):

> simd q gov proposals --node http://localhost:27000
pagination:
  next_key: null
  total: "0"
proposals:
- deposit_end_time: "2022-09-17T18:46:25.607417Z"
  final_tally_result:
    abstain_count: "0"
    no_count: "0"
    no_with_veto_count: "0"
    yes_count: "10000000"
  id: "1"
  messages:
  - '@type': /cosmos.gov.v1.MsgExecLegacyContent
    authority: cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn
    content:
      '@type': /ibc.core.client.v1.UpgradeProposal
      description: increase-unbonding-period
      plan:
        height: "200"
        info: ""
        name: increase-unbonding-period
        time: "0001-01-01T00:00:00Z"
        upgraded_client_state: null
      title: increase-unbonding-period
      upgraded_client_state:
        '@type': /ibc.lightclients.tendermint.v1.ClientState
        allow_update_after_expiry: false
        allow_update_after_misbehaviour: false
        chain_id: chain1
        frozen_height:
          revision_height: "0"
          revision_number: "0"
        latest_height:
          revision_height: "201"
          revision_number: "0"
        max_clock_drift: 0s
        proof_specs:
        - inner_spec:
            child_order:
            - 0
            - 1
            child_size: 33
            empty_child: null
            hash: SHA256
            max_prefix_length: 12
            min_prefix_length: 4
          leaf_spec:
            hash: SHA256
            length: VAR_PROTO
            prefix: AA==
            prehash_key: NO_HASH
            prehash_value: SHA256
          max_depth: 0
          min_depth: 0
        - inner_spec:
            child_order:
            - 0
            - 1
            child_size: 32
            empty_child: null
            hash: SHA256
            max_prefix_length: 1
            min_prefix_length: 1
          leaf_spec:
            hash: SHA256
            length: VAR_PROTO
            prefix: AA==
            prehash_key: NO_HASH
            prehash_value: SHA256
          max_depth: 0
          min_depth: 0
        trust_level:
          denominator: "0"
          numerator: "0"
        trusting_period: 0s
        unbonding_period: 2419200s
        upgrade_path:
        - upgrade
        - upgradedIBCState
  metadata: ""
  status: PROPOSAL_STATUS_PASSED
  submit_time: "2022-09-15T18:46:25.607417Z"
  total_deposit:
  - amount: "100"
    denom: stake
  voting_end_time: "2022-09-15T18:48:25.607417Z"
  voting_start_time: "2022-09-15T18:46:25.607417Z"

Step 3: update unbonding_time of staking params

Before we can create a connection between chain1 and chain2 we need to update the unbonding_time of the staking params. This is needed because chain1 will validate, during connection creation, that the counterparty chain2 has stored the correct values for the client state (which includes the unbonding period). To be able to perform the validation for the value of the unbonding period, chain1 will retrieve the unbonding_time from the staking keeper and therefore this value needs to match the unbonding_period in the client state stored on chain2 (which will be updated after the upgrade succeeds).

We use the REST interface to query the staking params of chain1 by simply entering http://localhost:27001/cosmos/staking/v1beta1/params on the address bar of the browser:

{
  "params": {
    "unbonding_time": "1814400s",
    "max_validators": 100,
    "max_entries": 7,
    "historical_entries": 10000,
    "bond_denom": "stake",
    "min_commission_rate": "0.000000000000000000"
  }
}

We submit a proposal to perform a param change:

simd tx gov submit-legacy-proposal param-change ./params_change.json \
--from validator \
--chain-id chain1 \
--keyring-backend test \
--home ../../gm/chain1 \
--node http://localhost:27000

with the following content in the param_change.json file:

{
  "title": "Staking Param Change",
  "description": "Update unbonding period",
  "changes": [
    {
      "subspace": "staking",
      "key": "UnbondingTime",
      "value": "2419200000000000"
    }
  ],
  "deposit": "100stake"
}

We check that the proposal has been accepted and it has entered the voting period:

> simd q gov proposals --node http://localhost:27000
- deposit_end_time: "2022-09-17T18:50:53.755022Z"
  final_tally_result:
    abstain_count: "0"
    no_count: "0"
    no_with_veto_count: "0"
    yes_count: "0"
  id: "2"
  messages:
  - '@type': /cosmos.gov.v1.MsgExecLegacyContent
    authority: cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn
    content:
      '@type': /cosmos.params.v1beta1.ParameterChangeProposal
      changes:
      - key: UnbondingTime
        subspace: staking
        value: '"2419200000000000"'
      description: Update unbonding period
      title: Staking Param Change
  metadata: ""
  status: PROPOSAL_STATUS_VOTING_PERIOD
  submit_time: "2022-09-15T18:50:53.755022Z"
  total_deposit:
  - amount: "100"
    denom: stake
  voting_end_time: "2022-09-15T18:52:53.755022Z"
  voting_start_time: "2022-09-15T18:50:53.755022Z"

Once the voting period ends, we check again and see that the proposal has passed:

> simd q gov proposals --node http://localhost:27000
- deposit_end_time: "2022-09-17T18:50:53.755022Z"
  final_tally_result:
    abstain_count: "0"
    no_count: "0"
    no_with_veto_count: "0"
    yes_count: "10000000"
  id: "2"
  messages:
  - '@type': /cosmos.gov.v1.MsgExecLegacyContent
    authority: cosmos10d07y265gmmuvt4z0w9aw880jnsr700j6zn9kn
    content:
      '@type': /cosmos.params.v1beta1.ParameterChangeProposal
      changes:
      - key: UnbondingTime
        subspace: staking
        value: '"2419200000000000"'
      description: Update unbonding period
      title: Staking Param Change
  metadata: ""
  status: PROPOSAL_STATUS_PASSED
  submit_time: "2022-09-15T18:50:53.755022Z"
  total_deposit:
  - amount: "100"
    denom: stake
  voting_end_time: "2022-09-15T18:52:53.755022Z"
  voting_start_time: "2022-09-15T18:50:53.755022Z"

If we make a query for the staking params of chain1 using the REST API http://localhost:27001/cosmos/staking/v1beta1/params we can see that the unbonding_time has been updated to 2419200 seconds:

{
  "params": {
    "unbonding_time": "2419200s",
    "max_validators": 100,
    "max_entries": 7,
    "historical_entries": 10000,
    "bond_denom": "stake",
    "min_commission_rate": "0.000000000000000000"
  }
}

Step 4: perform client upgrade and restart chain1

We can now instruct hermes to wait for chain1 to halt and then it will perform the upgrade for client 07-tendermint-0 on chain2.

hermes upgrade client --host-chain chain2 --client 07-tendermint-0 --upgrade-height 200

When chain1 halts we can check the logs of the chain and see that it has panicked at the expected block height:

[90m8:59PM[0m [1m[31mERR[0m[0m UPGRADE "increase-unbonding-period" NEEDED at height: 200: 
[90m8:59PM[0m [1m[31mERR[0m[0m CONSENSUS FAILURE!!! [36merr=[0m"UPGRADE \"increase-unbonding-period\" NEEDED at height: 200: " [36mmodule=[0mconsensus [36mstack=[0m"goroutine 88 [running]:\nruntime/debug.Stack()\n\truntime/debug/stack.go:24 +0x65\ngithub.com/tendermint/tendermint/consensus.(*State).receiveRoutine.func2()\n\tgithub.com/tendermint/[email protected]/consensus/state.go:727 +0x4c\npanic({0x575a120, 0xc001413d10})\n\truntime/panic.go:838 +0x207\ngithub.com/cosmos/cosmos-sdk/x/upgrade.BeginBlocker({{0x7ffeefbff6b0, 0x34}, 0xc00112ed20, {0x6478950, 0xc000e17210}, {0x6498f88, 0xc0005de420}, 0xc0013d33e0, {0x64716e0, 0xc000555180}, ...}, ...)\n\tgithub.com/cosmos/[email protected]/x/upgrade/abci.go:56 +0xf94\ngithub.com/cosmos/cosmos-sdk/x/upgrade.AppModule.BeginBlock(...)\n\tgithub.com/cosmos/[email protected]/x/upgrade/module.go:134\ngithub.com/cosmos/cosmos-sdk/types/module.(*Manager).BeginBlock(_, {{0x648d340, 0xc000126008}, {0x649a880, 0xc0075838c0}, {{0xb, 0x0}, {0xc001360470, 0x6}, 0xc8, ...}, ...}, ...)\n\tgithub.com/cosmos/[email protected]/types/module/module.go:481 +0x398\ngithub.com/cosmos/ibc-go/v5/testing/simapp.(*SimApp).BeginBlocker(_, {{0x648d340, 0xc000126008}, {0x649a880, 0xc0075838c0}, {{0xb, 0x0}, {0xc001360470, 0x6}, 0xc8, ...}, ...}, ...)\n\tgithub.com/cosmos/ibc-go/v5/testing/simapp/app.go:649 +0x7b\ngithub.com/cosmos/cosmos-sdk/baseapp.(*BaseApp).BeginBlock(_, {{0xc00715e580, 0x20, 0x20}, {{0xb, 0x0}, {0xc001360470, 0x6}, 0xc8, {0x107e2b0, ...}, ...}, ...})\n\tgithub.com/cosmos/[email protected]/baseapp/abci.go:200 +0x843\ngithub.com/tendermint/tendermint/abci/client.(*localClient).BeginBlockSync(_, {{0xc00715e580, 0x20, 0x20}, {{0xb, 0x0}, {0xc001360470, 0x6}, 0xc8, {0x107e2b0, ...}, ...}, ...})\n\tgithub.com/tendermint/[email protected]/abci/client/local_client.go:280 +0x118\ngithub.com/tendermint/tendermint/proxy.(*appConnConsensus).BeginBlockSync(_, {{0xc00715e580, 0x20, 0x20}, {{0xb, 0x0}, {0xc001360470, 0x6}, 0xc8, {0x107e2b0, ...}, ...}, ...})\n\tgithub.com/tendermint/[email protected]/proxy/app_conn.go:81 +0x55\ngithub.com/tendermint/tendermint/state.execBlockOnProxyApp({0x648e338?, 0xc0010d3440}, {0x6493d18, 0xc00141c410}, 0xc002fe1e00, {0x649a108, 0xc00000e1f8}, 0xc7?)\n\tgithub.com/tendermint/[email protected]/state/execution.go:307 +0x3dd\ngithub.com/tendermint/tendermint/state.(*BlockExecutor).ApplyBlock(_, {{{0xb, 0x0}, {0xc000de28f0, 0x7}}, {0xc000de28f8, 0x6}, 0x1, 0xc7, {{0xc007ac1920, ...}, ...}, ...}, ...)\n\tgithub.com/tendermint/[email protected]/state/execution.go:140 +0x171\ngithub.com/tendermint/tendermint/consensus.(*State).finalizeCommit(0xc00121ee00, 0xc8)\n\tgithub.com/tendermint/[email protected]/consensus/state.go:1659 +0xafd\ngithub.com/tendermint/tendermint/consensus.(*State).tryFinalizeCommit(0xc00121ee00, 0xc8)\n\tgithub.com/tendermint/[email protected]/consensus/state.go:1568 +0x2ff\ngithub.com/tendermint/tendermint/consensus.(*State).enterCommit.func1()\n\tgithub.com/tendermint/[email protected]/consensus/state.go:1503 +0x87\ngithub.com/tendermint/tendermint/consensus.(*State).enterCommit(0xc00121ee00, 0xc8, 0x0)\n\tgithub.com/tendermint/[email protected]/consensus/state.go:1541 +0xcb7\ngithub.com/tendermint/tendermint/consensus.(*State).addVote(0xc00121ee00, 0xc006757360, {0x0, 0x0})\n\tgithub.com/tendermint/[email protected]/consensus/state.go:2155 +0xb7c\ngithub.com/tendermint/tendermint/consensus.(*State).tryAddVote(0xc00121ee00, 0xc006757360, {0x0?, 0x4079c66?})\n\tgithub.com/tendermint/[email protected]/consensus/state.go:1953 +0x2c\ngithub.com/tendermint/tendermint/consensus.(*State).handleMsg(0xc00121ee00, {{0x64727a0?, 0xc0000bbd88?}, {0x0?, 0x0?}})\n\tgithub.com/tendermint/[email protected]/consensus/state.go:856 +0x44b\ngithub.com/tendermint/tendermint/consensus.(*State).receiveRoutine(0xc00121ee00, 0x0)\n\tgithub.com/tendermint/[email protected]/consensus/state.go:783 +0x512\ncreated by github.com/tendermint/tendermint/consensus.(*State).OnStart\n\tgithub.com/tendermint/[email protected]/consensus/state.go:379 +0x12d\n"

And hermes has performed the upgrade successfully:

SUCCESS [
  UpdateClient(
    h: 0-200, cs_h: 07-tendermint-0(0-200),
  ),
  UpgradeClient(
    UpgradeClient(
      Attributes {
        height: Height {
          revision: 0,
          height: 200,
        },
        client_id: ClientId(
          "07-tendermint-0",
        ),
        client_type: Tendermint,
        consensus_height: Height {
          revision: 0,
          height: 201,
        },
      },
    ),
  ),
]

We use ibc-go's REST interface to check the client state for chain1 on chain2 by simply entering http://localhost:27011/ibc/core/client/v1/client_states/07-tendermint-0 on the browser address bar:

{
  "client_state": {
    "@type": "/ibc.lightclients.tendermint.v1.ClientState",
    "chain_id": "chain1",
    "trust_level": {
      "numerator": "1",
      "denominator": "3"
    },
    "trusting_period": "1209600s",
    "unbonding_period": "2419200s",
    "max_clock_drift": "40s",
    "frozen_height": {
      "revision_number": "0",
      "revision_height": "0"
    },
    "latest_height": {
      "revision_number": "0",
      "revision_height": "201"
    },
    "proof_specs": [
      {
        "leaf_spec": {
          "hash": "SHA256",
          "prehash_key": "NO_HASH",
          "prehash_value": "SHA256",
          "length": "VAR_PROTO",
          "prefix": "AA=="
        },
        "inner_spec": {
          "child_order": [
            0,
            1
          ],
          "child_size": 33,
          "min_prefix_length": 4,
          "max_prefix_length": 12,
          "empty_child": null,
          "hash": "SHA256"
        },
        "max_depth": 0,
        "min_depth": 0
      },
      {
        "leaf_spec": {
          "hash": "SHA256",
          "prehash_key": "NO_HASH",
          "prehash_value": "SHA256",
          "length": "VAR_PROTO",
          "prefix": "AA=="
        },
        "inner_spec": {
          "child_order": [
            0,
            1
          ],
          "child_size": 32,
          "min_prefix_length": 1,
          "max_prefix_length": 1,
          "empty_child": null,
          "hash": "SHA256"
        },
        "max_depth": 0,
        "min_depth": 0
      }
    ],
    "upgrade_path": [
      "upgrade",
      "upgradedIBCState"
    ],
    "allow_update_after_expiry": true,
    "allow_update_after_misbehaviour": true
  },
  "proof": null,
  "proof_height": {
    "revision_number": "0",
    "revision_height": "230"
  }
}

And we can see that the unbonding_period has correctly been updated to 2419200s.

We know prepare a new binary for chain1 that include the following upgrade handler:

app.UpgradeKeeper.SetUpgradeHandler("increase-unbonding-period",
  func(ctx sdk.Context, _ upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) {
    return app.mm.RunMigrations(ctx, app.configurator, fromVM)
  })

We swap the binary for chain1 and restart:

[90m9:03PM[0m [32mINF[0m applying upgrade "increase-unbonding-period" at height: 200

The upgrade has concluded and chain1 is running again.

Bonus: create a connection between chain1 and chain2

We create a light client for chain2 on chain1:

hermes create client --host-chain chain1 --reference-chain chain2
SUCCESS CreateClient(
  CreateClient(
    Attributes {
      height: Height {
        revision: 0,
        height: 225,
      },
      client_id: ClientId(
        "07-tendermint-0",
      ),
      client_type: Tendermint,
      consensus_height: Height {
        revision: 0,
        height: 260,
      },
    },
  ),
)

Now both chain1 and chain2 have a light client of each other and we can now create a connection:

hermes --config config.toml create connection --a-chain chain1 --a-client 07-tendermint-0 --b-client 07-tendermint-0
SUCCESS Connection {
  delay_period: 0ns,
  a_side: ConnectionSide {
    chain: BaseChainHandle {
      chain_id: ChainId {
          id: "chain1",
          version: 0,
      },
      runtime_sender: Sender { .. },
    },
    client_id: ClientId(
      "07-tendermint-0",
    ),
    connection_id: Some(
      ConnectionId(
          "connection-0",
      ),
    ),
  },
  b_side: ConnectionSide {
    chain: BaseChainHandle {
      chain_id: ChainId {
        id: "chain2",
        version: 0,
      },
      runtime_sender: Sender { .. },
    },
    client_id: ClientId(
      "07-tendermint-0",
    ),
    connection_id: Some(
      ConnectionId(
          "connection-0",
      ),
    ),
  },
}

The connection creation succeeded, which means that chain1's validation for its client state stored on chain2 passed.