Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: make 09-localhost stateless #6683

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
614fb22
Remove client state from localhost light client
gjermundgaraba Jun 10, 2024
952423f
Initial store upgrade for v9 localhost statelessness
gjermundgaraba Jun 10, 2024
cc1fa35
Merge branch 'main' into gjermund/5959-make-localhost-a-stateless-imp…
gjermundgaraba Jun 21, 2024
fe6519f
Remove localhost creation and self state proof
gjermundgaraba Jun 21, 2024
249518b
Wire up migrations
gjermundgaraba Jun 21, 2024
251825d
Update docs for 09-localhost for 09-localhost
gjermundgaraba Jun 24, 2024
465a80c
Merge branch 'main' into gjermund/5959-make-localhost-a-stateless-imp…
gjermundgaraba Jun 24, 2024
bb92f22
lint
gjermundgaraba Jun 24, 2024
93082f5
Merge branch 'main' into gjermund/5959-make-localhost-a-stateless-imp…
gjermundgaraba Jun 24, 2024
0847cbe
Merge branch 'main' into gjermund/5959-make-localhost-a-stateless-imp…
gjermundgaraba Jun 26, 2024
f573061
Merge branch 'main' into gjermund/5959-make-localhost-a-stateless-imp…
crodriguezvega Jul 2, 2024
84c3236
refactor: simplify migrations, remove unnecessary iteration over clie…
colin-axner Jul 3, 2024
ec25710
refactor: remove localhost client state from queries
colin-axner Jul 3, 2024
c207393
refactor: remove localhost client state, condense localhost impl into…
colin-axner Jul 3, 2024
8321f41
rename: MigrateToStatelessLocalhost
colin-axner Jul 3, 2024
c58f93a
lint
colin-axner Jul 3, 2024
261231e
Merge branch 'main' of github.com:cosmos/ibc-go into gjermund/5959-ma…
colin-axner Jul 3, 2024
58ca2d9
Update docs/docs/05-migrations/13-v8-to-v9.md
colin-axner Jul 3, 2024
ab99084
Update docs/docs/05-migrations/13-v8-to-v9.md
colin-axner Jul 3, 2024
61f8ea6
review: docs + linting cleanups
colin-axner Jul 3, 2024
79b809f
Merge branch 'gjermund/5959-make-localhost-a-stateless-implementation…
colin-axner Jul 3, 2024
c107950
Merge branch 'main' of github.com:cosmos/ibc-go into gjermund/5959-ma…
colin-axner Jul 3, 2024
fdd7806
refactor: remove unused func
colin-axner Jul 3, 2024
a66ccff
Update modules/core/02-client/keeper/migrations.go
colin-axner Jul 3, 2024
6028aa0
markdown lint
colin-axner Jul 3, 2024
e00c792
Merge branch 'gjermund/5959-make-localhost-a-stateless-implementation…
colin-axner Jul 3, 2024
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
42 changes: 26 additions & 16 deletions docs/docs/03-light-clients/02-localhost/01-overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,43 @@ slug: /ibc/light-clients/localhost/overview
Learn about the 09-localhost light client module.
:::

The 09-localhost light client module implements a localhost loopback client with the ability to send and receive IBC packets to and from the same state machine.
The 09-localhost light client module implements a stateless localhost loopback client with the ability to send and
receive IBC packets to and from the same state machine.

### Context

In a multichain environment, application developers will be used to developing cross-chain applications through IBC. From their point of view, whether or not they are interacting with multiple modules on the same chain or on different chains should not matter. The localhost client module enables a unified interface to interact with different applications on a single chain, using the familiar IBC application layer semantics.
In a multichain environment, application developers will be used to developing cross-chain applications through IBC.
From their point of view, whether or not they are interacting with multiple modules on the same chain or on different
chains should not matter. The localhost client module enables a unified interface to interact with different
applications on a single chain, using the familiar IBC application layer semantics.

### Implementation

There exists a [single sentinel `ClientState`](03-client-state.md) instance with the client identifier `09-localhost`.
There exists a localhost light client module which can be invoked with the client identifier `09-localhost`. The light
client is stateless, so the `ClientState` is constructed on demand when required.

To supplement this, a [sentinel `ConnectionEnd` is stored in core IBC](04-connection.md) state with the connection identifier `connection-localhost`. This enables IBC applications to create channels directly on top of the sentinel connection which leverage the 09-localhost loopback functionality.
To supplement this, a [sentinel `ConnectionEnd` is stored in core IBC](04-connection.md) state with the connection
identifier `connection-localhost`. This enables IBC applications to create channels directly on top of the sentinel
connection which leverage the 09-localhost loopback functionality.

[State verification](05-state-verification.md) for channel state in handshakes or processing packets is reduced in complexity, the `09-localhost` client can simply compare bytes stored under the standardized key paths.
[State verification](05-state-verification.md) for channel state in handshakes or processing packets is reduced in
complexity, the `09-localhost` client can simply compare bytes stored under the standardized key paths.

### Localhost vs *regular* client

The localhost client aims to provide a unified approach to interacting with applications on a single chain, as the IBC application layer provides for cross-chain interactions. To achieve this unified interface though, there are a number of differences under the hood compared to a 'regular' IBC client (excluding `06-solomachine` and `09-localhost` itself).
The localhost client aims to provide a unified approach to interacting with applications on a single chain, as the IBC
application layer provides for cross-chain interactions. To achieve this unified interface though, there are a number of
differences under the hood compared to a 'regular' IBC client (excluding `06-solomachine` and `09-localhost` itself).

The table below lists some important differences:

| | Regular client | Localhost |
| -------------------------------------------- | --------------------------------------------------------------------------- | --------- |
| Number of clients | Many instances of a client *type* corresponding to different counterparties | A single sentinel client with the client identifier `09-localhost`|
| Client creation | Relayer (permissionless) | `ClientState` is instantiated in the `InitGenesis` handler of the 02-client submodule in core IBC |
| Client updates | Relayer submits headers using `MsgUpdateClient` | Latest height is updated periodically through the ABCI [`BeginBlock`](https://docs.cosmos.network/v0.47/building-modules/beginblock-endblock) interface of the 02-client submodule in core IBC |
| Number of connections | Many connections, 1 (or more) per client | A single sentinel connection with the connection identifier `connection-localhost` |
| Connection creation | Connection handshake, provided underlying client | Sentinel `ConnectionEnd` is created and set in store in the `InitGenesis` handler of the 03-connection submodule in core IBC |
| Counterparty | Underlying client, representing another chain | Client with identifier `09-localhost` in same chain |
| `VerifyMembership` and `VerifyNonMembership` | Performs proof verification using consensus state roots | Performs state verification using key-value lookups in the core IBC store |
| Integration | Expected to register codec types using the `AppModuleBasic` interface | Registers codec types within the core IBC module |
| | Regular client | Localhost |
|----------------------------------------------|-----------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------|
| Number of clients | Many instances of a client *type* corresponding to different counterparties | A single sentinel client with the client identifier `09-localhost` |
| Client creation | Relayer (permissionless) | Implicitly made available by the 02-client submodule in core IBC |
| Client updates | Relayer submits headers using `MsgUpdateClient` | No client updates are required as the localhost implementation is stateless |
| Number of connections | Many connections, 1 (or more) per client | A single sentinel connection with the connection identifier `connection-localhost` |
| Connection creation | Connection handshake, provided underlying client | Sentinel `ConnectionEnd` is created and set in store in the `InitGenesis` handler of the 03-connection submodule in core IBC |
| Counterparty | Underlying client, representing another chain | Client with identifier `09-localhost` in same chain |
| `VerifyMembership` and `VerifyNonMembership` | Performs proof verification using consensus state roots | Performs state verification using key-value lookups in the core IBC store |
| `ClientState` storage | `ClientState` stored and directly provable with `VerifyMembership` | Stateless, so `ClientState` is not provable directly with `VerifyMembership` |
60 changes: 1 addition & 59 deletions docs/docs/03-light-clients/02-localhost/03-client-state.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,64 +5,6 @@ sidebar_position: 3
slug: /ibc/light-clients/localhost/client-state
---


# `ClientState`

The 09-localhost `ClientState` maintains a single field used to track the latest sequence of the state machine i.e. the height of the blockchain.

```go
type ClientState struct {
// the latest height of the blockchain
LatestHeight clienttypes.Height
}
```

The 09-localhost `ClientState` is instantiated in the `InitGenesis` handler of the 02-client submodule in core IBC.
It calls `CreateLocalhostClient`, declaring a new `ClientState` and initializing it with its own client prefixed store.

```go
func (k Keeper) CreateLocalhostClient(ctx sdk.Context) error {
clientModule, found := k.router.GetRoute(exported.LocalhostClientID)
if !found {
return errorsmod.Wrap(types.ErrRouteNotFound, exported.LocalhostClientID)
}

return clientModule.Initialize(ctx, exported.LocalhostClientID, nil, nil)
}
```

It is possible to disable the localhost client by removing the `09-localhost` entry from the `allowed_clients` list through governance.

## Client updates

The latest height is updated periodically through the ABCI [`BeginBlock`](https://docs.cosmos.network/v0.47/building-modules/beginblock-endblock) interface of the 02-client submodule in core IBC.

[See `BeginBlocker` in abci.go.](https://github.com/cosmos/ibc-go/blob/v8.1.1/modules/core/02-client/abci.go#L12)

```go
func BeginBlocker(ctx sdk.Context, k keeper.Keeper) {
// ...

if clientState, found := k.GetClientState(ctx, exported.Localhost); found {
if k.GetClientStatus(ctx, clientState, exported.Localhost) == exported.Active {
k.UpdateLocalhostClient(ctx, clientState)
}
}
}
```

The above calls into the 09-localhost `UpdateState` method of the `ClientState` .
It retrieves the current block height from the application context and sets the `LatestHeight` of the 09-localhost client.

```go
func (cs ClientState) UpdateState(ctx sdk.Context, cdc codec.BinaryCodec, clientStore sdk.KVStore, clientMsg exported.ClientMessage) []exported.Height {
height := clienttypes.GetSelfHeight(ctx)
cs.LatestHeight = height

clientStore.Set(host.ClientStateKey(), clienttypes.MustMarshalClientState(cdc, &cs))

return []exported.Height{height}
}
```

Note that the 09-localhost `ClientState` is not updated through the 02-client interface leveraged by conventional IBC light clients.
The 09-localhost client is stateless and has no types.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ sidebar_position: 5
slug: /ibc/light-clients/localhost/state-verification
---


# State verification

The localhost client handles state verification through the `LightClientModule` interface methods `VerifyMembership` and `VerifyNonMembership` by performing read-only operations directly on the core IBC store.
Expand All @@ -20,3 +19,5 @@ The 09-localhost light client module defines a `SentinelProof` as a single byte.
```go
var SentinelProof = []byte{0x01}
```

The `ClientState` of `09-localhost` is stateless, so it is not directly provable with `VerifyMembership` or `VerifyNonMembership`.
8 changes: 8 additions & 0 deletions docs/docs/05-migrations/13-v8-to-v9.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ func NewMsgModuleQuerySafe(
- The utility function `QueryLatestConsensusState` of `04-channel` CLI has been removed.
- `UnmarshalPacketData` now takes in the context, portID, and channelID. This allows the packet data to be unmarshaled based on the channel version.
- `Router` reference has been removed from IBC core keeper: [#6138](https://github.com/cosmos/ibc-go/pull/6138). Please use `PortKeeper.Router` instead.
- The function `CreateLocalhostClient` has been removed. The localhost client is now stateless.

### 02-client

Expand Down Expand Up @@ -185,3 +186,10 @@ The `IterateConsensusMetadata` function has been removed.
- The `VerifyMembershipMsg` and `VerifyNonMembershipMsg` payloads for `SudoMsg` have been extended to include a new field, `MerklePath`. The existing `Path` field will remain the same. The new `MerklePath` field is used if and only if the provided key path contains non-utf8 encoded symbols, and as a result will encode the JSON field `merkle_path` as a base64 encoded bytestring. See [23-commitment](#23-commitment).
- The `ExportMetadataMsg` struct has been removed and is no longer required for contracts to implement. Core IBC will handle exporting all key/value's written to the store by a light client contract.
- The `ZeroCustomFields` interface function has been removed from the `ClientState` interface. Core IBC only used this function to set tendermint client states when scheduling an IBC software upgrade. The interface function has been replaced by a type assertion.

### 09-localhost

The `09-localhost` light client has been made stateless and will no longer update the client on every block. The `ClientState` is constructed on demand when required.
The `ClientState` itself is therefore no longer provable direcly with `VerifyMembership` or `VerifyNonMembership`.

Previously stored client state data is pruned automatically on IBC module store migration from `ConsensusVersion` 6 to 7.
4 changes: 4 additions & 0 deletions e2e/tests/core/02-client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,10 @@ func (s *ClientTestSuite) TestAllowedClientsParam() {
status, err := query.ClientStatus(ctx, chainA, ibctesting.FirstClientID)
s.Require().NoError(err)
s.Require().Equal(ibcexported.Unauthorized.String(), status)

status, err = query.ClientStatus(ctx, chainA, ibcexported.Localhost)
s.Require().NoError(err)
s.Require().Equal(ibcexported.Unauthorized.String(), status)
})
}

Expand Down
15 changes: 0 additions & 15 deletions e2e/tests/transfer/localhost_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,21 +53,6 @@ func (s *LocalhostTransferTestSuite) TestMsgTransfer_Localhost() {

s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA), "failed to wait for blocks")

t.Run("verify begin blocker was executed", func(t *testing.T) {
cs, err := query.ClientState(ctx, chainA, exported.LocalhostClientID)
s.Require().NoError(err)

localhostClientState, ok := cs.(*localhost.ClientState)
s.Require().True(ok)
originalHeight := localhostClientState.LatestHeight

s.Require().NoError(test.WaitForBlocks(ctx, 1, chainA), "failed to wait for blocks")

cs, err = query.ClientState(ctx, chainA, exported.LocalhostClientID)
s.Require().NoError(err)
s.Require().True(cs.(*localhost.ClientState).LatestHeight.GT(originalHeight), "client state height was not incremented")
})

t.Run("channel open init localhost", func(t *testing.T) {
msgChanOpenInit := channeltypes.NewMsgChannelOpenInit(
transfertypes.PortID, channelA.Version,
Expand Down
2 changes: 0 additions & 2 deletions e2e/testsuite/codec.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ import (
channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types"
solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine"
ibctmtypes "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint"
localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost"
ibctesting "github.com/cosmos/ibc-go/v8/testing"
simappparams "github.com/cosmos/ibc-go/v8/testing/simapp/params"
)
Expand Down Expand Up @@ -76,7 +75,6 @@ func codecAndEncodingConfig() (*codec.ProtoCodec, simappparams.EncodingConfig) {
channeltypes.RegisterInterfaces(cfg.InterfaceRegistry)
connectiontypes.RegisterInterfaces(cfg.InterfaceRegistry)
ibctmtypes.RegisterInterfaces(cfg.InterfaceRegistry)
localhost.RegisterInterfaces(cfg.InterfaceRegistry)
wasmtypes.RegisterInterfaces(cfg.InterfaceRegistry)

// all other types
Expand Down
8 changes: 0 additions & 8 deletions modules/core/02-client/abci.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (

"github.com/cosmos/ibc-go/v8/modules/core/02-client/keeper"
"github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
"github.com/cosmos/ibc-go/v8/modules/core/exported"
ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint"
)

Expand Down Expand Up @@ -33,11 +32,4 @@ func BeginBlocker(ctx sdk.Context, k *keeper.Keeper) {
keeper.EmitUpgradeChainEvent(ctx, plan.Height)
}
}

// update the localhost client with the latest block height if it is active.
if clientState, found := k.GetClientState(ctx, exported.Localhost); found {
if k.GetClientStatus(ctx, exported.LocalhostClientID) == exported.Active {
k.UpdateLocalhostClient(ctx, clientState)
}
}
}
6 changes: 0 additions & 6 deletions modules/core/02-client/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,6 @@ func InitGenesis(ctx sdk.Context, k *keeper.Keeper, gs types.GenesisState) {
}

k.SetNextClientSequence(ctx, gs.NextClientSequence)

// if the localhost already exists in state (included in the genesis file),
// it must be overwritten to ensure its stored height equals the context block height
if err := k.CreateLocalhostClient(ctx); err != nil {
panic(fmt.Errorf("failed to initialise localhost client: %s", err.Error()))
}
}

// ExportGenesis returns the ibc client submodule's exported genesis.
Expand Down
6 changes: 1 addition & 5 deletions modules/core/02-client/keeper/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"github.com/cosmos/ibc-go/v8/modules/core/exported"
solomachine "github.com/cosmos/ibc-go/v8/modules/light-clients/06-solomachine"
ibctm "github.com/cosmos/ibc-go/v8/modules/light-clients/07-tendermint"
localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost"
ibctesting "github.com/cosmos/ibc-go/v8/testing"
)

Expand Down Expand Up @@ -64,10 +63,7 @@ func (suite *KeeperTestSuite) TestCreateClient() {
},
{
"failure: 09-localhost client type not supported",
func() {
lhClientState := localhost.NewClientState(clienttypes.GetSelfHeight(suite.chainA.GetContext()))
clientState = suite.chainA.App.AppCodec().MustMarshal(lhClientState)
},
func() {},
exported.Localhost,
false,
},
Expand Down
20 changes: 2 additions & 18 deletions modules/core/02-client/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,26 +119,11 @@ func (suite *KeeperTestSuite) TestQueryClientStates() {
{
"empty pagination",
func() {
localhost := types.NewIdentifiedClientState(exported.LocalhostClientID, suite.chainA.GetClientState(exported.LocalhostClientID))
expClientStates = types.IdentifiedClientStates{localhost}
expClientStates = nil
req = &types.QueryClientStatesRequest{}
},
true,
},
{
"success, only localhost",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

grpc should no longer return localhost in its query. There's no information to obtain, as it would just return back the height of the request

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that makes sense!

func() {
localhost := types.NewIdentifiedClientState(exported.LocalhostClientID, suite.chainA.GetClientState(exported.LocalhostClientID))
expClientStates = types.IdentifiedClientStates{localhost}
req = &types.QueryClientStatesRequest{
Pagination: &query.PageRequest{
Limit: 3,
CountTotal: true,
},
}
},
true,
},
{
"success",
func() {
Expand All @@ -151,12 +136,11 @@ func (suite *KeeperTestSuite) TestQueryClientStates() {
clientStateA1 := path1.EndpointA.GetClientState()
clientStateA2 := path2.EndpointA.GetClientState()

localhost := types.NewIdentifiedClientState(exported.LocalhostClientID, suite.chainA.GetClientState(exported.LocalhostClientID))
idcs := types.NewIdentifiedClientState(path1.EndpointA.ClientID, clientStateA1)
idcs2 := types.NewIdentifiedClientState(path2.EndpointA.ClientID, clientStateA2)

// order is sorted by client id
expClientStates = types.IdentifiedClientStates{localhost, idcs, idcs2}.Sort()
expClientStates = types.IdentifiedClientStates{idcs, idcs2}.Sort()
req = &types.QueryClientStatesRequest{
Pagination: &query.PageRequest{
Limit: 20,
Expand Down
10 changes: 0 additions & 10 deletions modules/core/02-client/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,6 @@ func (k *Keeper) Route(clientID string) (exported.LightClientModule, bool) {
return k.router.GetRoute(clientID)
}

// CreateLocalhostClient initialises the 09-localhost client state and sets it in state.
func (k *Keeper) CreateLocalhostClient(ctx sdk.Context) error {
clientModule, found := k.router.GetRoute(exported.LocalhostClientID)
if !found {
return errorsmod.Wrap(types.ErrRouteNotFound, exported.LocalhostClientID)
}

return clientModule.Initialize(ctx, exported.LocalhostClientID, nil, nil)
}

// UpdateLocalhostClient updates the 09-localhost client to the latest block height and chain ID.
func (k *Keeper) UpdateLocalhostClient(ctx sdk.Context, clientState exported.ClientState) []exported.Height {
clientModule, found := k.router.GetRoute(exported.LocalhostClientID)
Expand Down
Loading
Loading