Skip to content

Commit

Permalink
chore: rename proofXyz -> xyzProof (#5599)
Browse files Browse the repository at this point in the history
* chore: rename proofXyz -> xyzProof

* more renaming

* rename VerifyChannelUpgradeError proof parameters for consistency with the other verification functions

* remove counterparty prefix from proof names

---------

Co-authored-by: DimitrisJim <[email protected]>
(cherry picked from commit 9184de3)

# Conflicts:
#	docs/docs/03-light-clients/01-developer-guide/05-upgrades.md
#	modules/core/02-client/keeper/client_test.go
#	modules/core/04-channel/types/msgs.go
#	modules/core/ante/ante_test.go
#	modules/core/keeper/msg_server_test.go
#	modules/light-clients/08-wasm/types/upgrade.go
#	modules/light-clients/08-wasm/types/upgrade_test.go
  • Loading branch information
crodriguezvega authored and mergify[bot] committed Jan 16, 2024
1 parent bc0f5e4 commit baed043
Show file tree
Hide file tree
Showing 28 changed files with 686 additions and 271 deletions.
66 changes: 66 additions & 0 deletions docs/docs/03-light-clients/01-developer-guide/05-upgrades.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
---
title: Handling Upgrades
sidebar_label: Handling Upgrades
sidebar_position: 5
slug: /ibc/light-clients/upgrades
---


# Handling upgrades

It is vital that high-value IBC clients can upgrade along with their underlying chains to avoid disruption to the IBC ecosystem. Thus, IBC client developers will want to implement upgrade functionality to enable clients to maintain connections and channels even across chain upgrades.

## Implementing `VerifyUpgradeAndUpdateState`

The IBC protocol allows client implementations to provide a path to upgrading clients given the upgraded `ClientState`, upgraded `ConsensusState` and proofs for each. This path is provided in the `VerifyUpgradeAndUpdateState` method:

```go
// NOTE: proof heights are not included as upgrade to a new revision is expected to pass only on the last height committed by the current revision. Clients are responsible for ensuring that the planned last height of the current revision is somehow encoded in the proof verification process.
// This is to ensure that no premature upgrades occur, since upgrade plans committed to by the counterparty may be cancelled or modified before the last planned height.
// If the upgrade is verified, the upgraded client and consensus states must be set in the client store.
func (cs ClientState) VerifyUpgradeAndUpdateState(
ctx sdk.Context,
cdc codec.BinaryCodec,
store sdk.KVStore,
newClient ClientState,
newConsState ConsensusState,
upgradeClientProof,
upgradeConsensusStateProof []byte,
) error
```

> Please refer to the [Tendermint light client implementation](https://github.com/cosmos/ibc-go/blob/v7.0.0/modules/light-clients/07-tendermint/upgrade.go#L27) as an example for implementation.

It is important to note that light clients **must** handle all management of client and consensus states including the setting of updated `ClientState` and `ConsensusState` in the client store. This can include verifying that the submitted upgraded `ClientState` is of a valid `ClientState` type, that the height of the upgraded client is not greater than the height of the current client (in order to preserve BFT monotonic time), or that certain parameters which should not be changed have not been altered in the upgraded `ClientState`.

Developers must ensure that the `MsgUpgradeClient` does not pass until the last height of the old chain has been committed, and after the chain upgrades, the `MsgUpgradeClient` should pass once and only once on all counterparty clients.

### Upgrade path

Clients should have **prior knowledge of the merkle path** that the upgraded client and upgraded consensus states will use. The height at which the upgrade has occurred should also be encoded in the proof.
> The Tendermint client implementation accomplishes this by including an `UpgradePath` in the `ClientState` itself, which is used along with the upgrade height to construct the merkle path under which the client state and consensus state are committed.

## Chain specific vs client specific client parameters

Developers should maintain the distinction between client parameters that are uniform across every valid light client of a chain (chain-chosen parameters), and client parameters that are customizable by each individual client (client-chosen parameters); since this distinction is necessary to implement the `ZeroCustomFields` method in the [`ClientState` interface](02-client-state.md):

```go
// Utility function that zeroes out any client customizable fields in client state
// Ledger enforced fields are maintained while all custom fields are zero values
// Used to verify upgrades
func (cs ClientState) ZeroCustomFields() ClientState
```

Developers must ensure that the new client adopts all of the new client parameters that must be uniform across every valid light client of a chain (chain-chosen parameters), while maintaining the client parameters that are customizable by each individual client (client-chosen parameters) from the previous version of the client. `ZeroCustomFields` is a useful utility function to ensure only chain specific fields are updated during upgrades.

## Security

Upgrades must adhere to the IBC Security Model. IBC does not rely on the assumption of honest relayers for correctness. Thus users should not have to rely on relayers to maintain client correctness and security (though honest relayers must exist to maintain relayer liveness). While relayers may choose any set of client parameters while creating a new `ClientState`, this still holds under the security model since users can always choose a relayer-created client that suits their security and correctness needs or create a client with their desired parameters if no such client exists.

However, when upgrading an existing client, one must keep in mind that there are already many users who depend on this client's particular parameters. **We cannot give the upgrading relayer free choice over these parameters once they have already been chosen. This would violate the security model** since users who rely on the client would have to rely on the upgrading relayer to maintain the same level of security.

Thus, developers must make sure that their upgrade mechanism allows clients to upgrade the chain-specified parameters whenever a chain upgrade changes these parameters (examples in the Tendermint client include `UnbondingPeriod`, `TrustingPeriod`, `ChainID`, `UpgradePath`, etc), while ensuring that the relayer submitting the `MsgUpgradeClient` cannot alter the client-chosen parameters that the users are relying upon (examples in Tendermint client include `TrustLevel`, `MaxClockDrift`, etc). The previous paragraph discusses how `ZeroCustomFields` helps achieve this.

### Document potential client parameter conflicts during upgrades

Counterparty clients can upgrade securely by using all of the chain-chosen parameters from the chain-committed `UpgradedClient` and preserving all of the old client-chosen parameters. This enables chains to securely upgrade without relying on an honest relayer, however it can in some cases lead to an invalid final `ClientState` if the new chain-chosen parameters clash with the old client-chosen parameter. This can happen in the Tendermint client case if the upgrading chain lowers the `UnbondingPeriod` (chain-chosen) to a duration below that of a counterparty client's `TrustingPeriod` (client-chosen). Such cases should be clearly documented by developers, so that chains know which upgrades should be avoided to prevent this problem. The final upgraded client should also be validated in `VerifyUpgradeAndUpdateState` before returning to ensure that the client does not upgrade to an invalid `ClientState`.
Original file line number Diff line number Diff line change
Expand Up @@ -280,10 +280,10 @@ func (suite *InterchainAccountsTestSuite) TestChanOpenTry() {
suite.Require().NoError(err)

channelKey := host.ChannelKey(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)
proofInit, proofHeight := path.EndpointB.Chain.QueryProof(channelKey)
initProof, proofHeight := path.EndpointB.Chain.QueryProof(channelKey)

// use chainA (controller) for ChanOpenTry
msg := channeltypes.NewMsgChannelOpenTry(path.EndpointA.ChannelConfig.PortID, TestVersion, channeltypes.ORDERED, []string{path.EndpointA.ConnectionID}, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, TestVersion, proofInit, proofHeight, icatypes.ModuleName)
msg := channeltypes.NewMsgChannelOpenTry(path.EndpointA.ChannelConfig.PortID, TestVersion, channeltypes.ORDERED, []string{path.EndpointA.ConnectionID}, path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, TestVersion, initProof, proofHeight, icatypes.ModuleName)
handler := suite.chainA.GetSimApp().MsgServiceRouter().Handler(msg)
_, err = handler(suite.chainA.GetContext(), msg)

Expand Down Expand Up @@ -427,10 +427,10 @@ func (suite *InterchainAccountsTestSuite) TestChanOpenConfirm() {

// query proof from ChainB
channelKey := host.ChannelKey(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID)
proofAck, proofHeight := path.EndpointB.Chain.QueryProof(channelKey)
ackProof, proofHeight := path.EndpointB.Chain.QueryProof(channelKey)

// use chainA (controller) for ChanOpenConfirm
msg := channeltypes.NewMsgChannelOpenConfirm(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, proofAck, proofHeight, icatypes.ModuleName)
msg := channeltypes.NewMsgChannelOpenConfirm(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID, ackProof, proofHeight, icatypes.ModuleName)
handler := suite.chainA.GetSimApp().MsgServiceRouter().Handler(msg)
_, err = handler(suite.chainA.GetContext(), msg)

Expand Down
4 changes: 2 additions & 2 deletions modules/apps/27-interchain-accounts/host/ibc_module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,10 +257,10 @@ func (suite *InterchainAccountsTestSuite) TestChanOpenAck() {

// query proof from ChainA
channelKey := host.ChannelKey(path.EndpointA.ChannelConfig.PortID, path.EndpointA.ChannelID)
proofTry, proofHeight := path.EndpointA.Chain.QueryProof(channelKey)
tryProof, proofHeight := path.EndpointA.Chain.QueryProof(channelKey)

// use chainB (host) for ChanOpenAck
msg := channeltypes.NewMsgChannelOpenAck(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, path.EndpointA.ChannelID, TestVersion, proofTry, proofHeight, icatypes.ModuleName)
msg := channeltypes.NewMsgChannelOpenAck(path.EndpointB.ChannelConfig.PortID, path.EndpointB.ChannelID, path.EndpointA.ChannelID, TestVersion, tryProof, proofHeight, icatypes.ModuleName)
handler := suite.chainB.GetSimApp().MsgServiceRouter().Handler(msg)
_, err = handler(suite.chainB.GetContext(), msg)

Expand Down
6 changes: 3 additions & 3 deletions modules/core/02-client/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,10 @@ func newUpgradeClientCmd() *cobra.Command {
}
}

proofUpgradeClient := []byte(args[3])
proofUpgradeConsensus := []byte(args[4])
upgradeClientProof := []byte(args[3])
upgradeConsensusProof := []byte(args[4])

msg, err := types.NewMsgUpgradeClient(clientID, clientState, consensusState, proofUpgradeClient, proofUpgradeConsensus, clientCtx.GetFromAddress().String())
msg, err := types.NewMsgUpgradeClient(clientID, clientState, consensusState, upgradeClientProof, upgradeConsensusProof, clientCtx.GetFromAddress().String())
if err != nil {
return err
}
Expand Down
4 changes: 2 additions & 2 deletions modules/core/02-client/keeper/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ func (k Keeper) UpdateClient(ctx sdk.Context, clientID string, clientMsg exporte
// UpgradeClient upgrades the client to a new client state if this new client was committed to
// by the old client at the specified upgrade height
func (k Keeper) UpgradeClient(ctx sdk.Context, clientID string, upgradedClient exported.ClientState, upgradedConsState exported.ConsensusState,
proofUpgradeClient, proofUpgradeConsState []byte,
upgradeClientProof, upgradeConsensusStateProof []byte,
) error {
clientState, found := k.GetClientState(ctx, clientID)
if !found {
Expand All @@ -129,7 +129,7 @@ func (k Keeper) UpgradeClient(ctx sdk.Context, clientID string, upgradedClient e
}

if err := clientState.VerifyUpgradeAndUpdateState(ctx, k.cdc, clientStore,
upgradedClient, upgradedConsState, proofUpgradeClient, proofUpgradeConsState,
upgradedClient, upgradedConsState, upgradeClientProof, upgradeConsensusStateProof,
); err != nil {
return errorsmod.Wrapf(err, "cannot upgrade client with ID %s", clientID)
}
Expand Down
62 changes: 47 additions & 15 deletions modules/core/02-client/keeper/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -308,12 +308,12 @@ func (suite *KeeperTestSuite) TestUpdateClientTendermint() {

func (suite *KeeperTestSuite) TestUpgradeClient() {
var (
path *ibctesting.Path
upgradedClient exported.ClientState
upgradedConsState exported.ConsensusState
lastHeight exported.Height
proofUpgradedClient, proofUpgradedConsState []byte
upgradedClientBz, upgradedConsStateBz []byte
path *ibctesting.Path
upgradedClient exported.ClientState
upgradedConsState exported.ConsensusState
lastHeight exported.Height
upgradedClientProof, upgradedConsensusStateProof []byte
upgradedClientBz, upgradedConsStateBz []byte
)

testCases := []struct {
Expand Down Expand Up @@ -341,8 +341,8 @@ func (suite *KeeperTestSuite) TestUpgradeClient() {
cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID)
suite.Require().True(found)

proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())
proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())
upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())
upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())
},
expPass: true,
},
Expand All @@ -367,8 +367,8 @@ func (suite *KeeperTestSuite) TestUpgradeClient() {
cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID)
suite.Require().True(found)

proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())
proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())
upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())
upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())

path.EndpointA.ClientID = "wrongclientid"
},
Expand Down Expand Up @@ -397,8 +397,8 @@ func (suite *KeeperTestSuite) TestUpgradeClient() {
cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID)
suite.Require().True(found)

proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())
proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())
upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())
upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())

// set frozen client in store
tmClient, ok := cs.(*ibctm.ClientState)
Expand Down Expand Up @@ -432,11 +432,43 @@ func (suite *KeeperTestSuite) TestUpgradeClient() {
cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID)
suite.Require().True(found)

proofUpgradedClient, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())
proofUpgradedConsState, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())
upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())
upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())
},
expPass: false,
},
<<<<<<< HEAD

Check failure on line 440 in modules/core/02-client/keeper/client_test.go

View workflow job for this annotation

GitHub Actions / tests (01)

expected operand, found '<<'
=======
{
name: "unsuccessful upgrade: upgraded height is not greater than current height",
setup: func() {
// last Height is at next block
lastHeight = clienttypes.NewHeight(1, uint64(suite.chainB.GetContext().BlockHeight()+1))

// zero custom fields and store in upgrade store
err := suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedClient(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedClientBz)
suite.Require().NoError(err)
err = suite.chainB.GetSimApp().UpgradeKeeper.SetUpgradedConsensusState(suite.chainB.GetContext(), int64(lastHeight.GetRevisionHeight()), upgradedConsStateBz)
suite.Require().NoError(err)

// change upgradedClient height to be lower than current client state height
tmClient := upgradedClient.(*ibctm.ClientState)
tmClient.LatestHeight = clienttypes.NewHeight(0, 1)
upgradedClient = tmClient

suite.coordinator.CommitBlock(suite.chainB)
err = path.EndpointA.UpdateClient()
suite.Require().NoError(err)

cs, found := suite.chainA.App.GetIBCKeeper().ClientKeeper.GetClientState(suite.chainA.GetContext(), path.EndpointA.ClientID)
suite.Require().True(found)

upgradedClientProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedClientKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())
upgradedConsensusStateProof, _ = suite.chainB.QueryUpgradeProof(upgradetypes.UpgradedConsStateKey(int64(lastHeight.GetRevisionHeight())), cs.GetLatestHeight().GetRevisionHeight())
},
expPass: false,
},
>>>>>>> 9184de36 (chore: rename `proofXyz` -> `xyzProof` (#5599))
}

for _, tc := range testCases {
Expand All @@ -463,7 +495,7 @@ func (suite *KeeperTestSuite) TestUpgradeClient() {

tc.setup()

err = suite.chainA.App.GetIBCKeeper().ClientKeeper.UpgradeClient(suite.chainA.GetContext(), path.EndpointA.ClientID, upgradedClient, upgradedConsState, proofUpgradedClient, proofUpgradedConsState)
err = suite.chainA.App.GetIBCKeeper().ClientKeeper.UpgradeClient(suite.chainA.GetContext(), path.EndpointA.ClientID, upgradedClient, upgradedConsState, upgradedClientProof, upgradedConsensusStateProof)

if tc.expPass {
suite.Require().NoError(err, "verify upgrade failed on valid case: %s", tc.name)
Expand Down
6 changes: 3 additions & 3 deletions modules/core/02-client/types/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ func (msg MsgUpdateClient) UnpackInterfaces(unpacker codectypes.AnyUnpacker) err

// NewMsgUpgradeClient creates a new MsgUpgradeClient instance
func NewMsgUpgradeClient(clientID string, clientState exported.ClientState, consState exported.ConsensusState,
proofUpgradeClient, proofUpgradeConsState []byte, signer string,
upgradeClientProof, upgradeConsensusStateProof []byte, signer string,
) (*MsgUpgradeClient, error) {
anyClient, err := PackClientState(clientState)
if err != nil {
Expand All @@ -166,8 +166,8 @@ func NewMsgUpgradeClient(clientID string, clientState exported.ClientState, cons
ClientId: clientID,
ClientState: anyClient,
ConsensusState: anyConsState,
ProofUpgradeClient: proofUpgradeClient,
ProofUpgradeConsensusState: proofUpgradeConsState,
ProofUpgradeClient: upgradeClientProof,
ProofUpgradeConsensusState: upgradeConsensusStateProof,
Signer: signer,
}, nil
}
Expand Down
Loading

0 comments on commit baed043

Please sign in to comment.