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 10 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
43 changes: 27 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,44 @@
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 [single sentinel `ClientState`](03-client-state.md) 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 `ClientState` is constructed with the latest height when queried |
| 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 |
| `ClientState` storage | `ClientState` stored and directly provable with `VerifyMembership` | Stateless, so `ClientState` is not provable directly with `VerifyMembership` |

Check failure on line 57 in docs/docs/03-light-clients/02-localhost/01-overview.md

View workflow job for this annotation

GitHub Actions / lint

Files should end with a single newline character

docs/docs/03-light-clients/02-localhost/01-overview.md:57:253 MD047/single-trailing-newline Files should end with a single newline character https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md047.md
49 changes: 6 additions & 43 deletions docs/docs/03-light-clients/02-localhost/03-client-state.md
Copy link
Contributor

Choose a reason for hiding this comment

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

unsure if we want to delete this file, but left for now

Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@

# `ClientState`

Even though the 09-localhost light client is a stateless client, it still has the concept of a `ClientState` that follows the
blockchains own latest height automatically. The `ClientState` is constructed on demand when required.

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
Expand All @@ -17,52 +20,12 @@
}
```

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)
}
```
The 09-localhost `ClientState` is available from the 02-client submodule in core IBC, and does not need to be initialized.

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}
}
```
The 09-localhost `ClientState` is stateless, so no client updates are required. The `ClientState` is constructed with the latest height when queried.
It will always follow latest height of the blockchain.

Note that the 09-localhost `ClientState` is not updated through the 02-client interface leveraged by conventional IBC light clients.
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,7 @@
```go
var SentinelProof = []byte{0x01}
```

The `ClientState` of `09-localhost` is stateless, so it is not directly provable with `VerifyMembership` or `VerifyNonMembership`.
Instead, the `ClientState` is constructed on demand when required.

7 changes: 7 additions & 0 deletions docs/docs/05-migrations/13-v8-to-v9.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,10 @@

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 updating the client on every block. The `ClientState` is constructed on demand when required.
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
The `ClientState` itself is therefore no longer provable direcly with `VerifyMembership` or `VerifyNonMembership`.

Previously stored client state date is pruned automatically on IBC module store migration from `ConsensusVersion` 6 to 7.

Check failure on line 150 in docs/docs/05-migrations/13-v8-to-v9.md

View workflow job for this annotation

GitHub Actions / lint

Files should end with a single newline character

docs/docs/05-migrations/13-v8-to-v9.md:150:121 MD047/single-trailing-newline Files should end with a single newline character https://github.com/DavidAnson/markdownlint/blob/v0.34.0/doc/md047.md
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
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
10 changes: 8 additions & 2 deletions modules/core/02-client/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
host "github.com/cosmos/ibc-go/v8/modules/core/24-host"
"github.com/cosmos/ibc-go/v8/modules/core/exported"
localhost "github.com/cosmos/ibc-go/v8/modules/light-clients/09-localhost"
)

var _ types.QueryServer = (*Keeper)(nil)
Expand Down Expand Up @@ -63,7 +64,7 @@ func (k *Keeper) ClientStates(c context.Context, req *types.QueryClientStatesReq

ctx := sdk.UnwrapSDKContext(c)

var clientStates types.IdentifiedClientStates
clientStates := types.IdentifiedClientStates{types.NewIdentifiedClientState(exported.LocalhostClientID, localhost.NewClientState(types.GetSelfHeight(ctx)))}
store := prefix.NewStore(ctx.KVStore(k.storeKey), host.KeyClientStorePrefix)

pageRes, err := query.FilteredPaginate(store, req.Pagination, func(key, value []byte, accumulate bool) (bool, error) {
Expand Down Expand Up @@ -93,9 +94,14 @@ func (k *Keeper) ClientStates(c context.Context, req *types.QueryClientStatesReq

sort.Sort(clientStates)

pagination := &query.PageResponse{
Total: pageRes.Total + 1, // To account for localhost client
NextKey: pageRes.NextKey,
}

return &types.QueryClientStatesResponse{
ClientStates: clientStates,
Pagination: pageRes,
Pagination: pagination,
}, nil
}

Expand Down
21 changes: 11 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 Expand Up @@ -109,6 +99,10 @@ func (k *Keeper) GenerateClientIdentifier(ctx sdk.Context, clientType string) st

// GetClientState gets a particular client from the store
func (k *Keeper) GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool) {
if clientID == exported.LocalhostClientID {
return localhost.NewClientState(types.GetSelfHeight(ctx)), true
}

store := k.ClientStore(ctx, clientID)
bz := store.Get(host.ClientStateKey())
if len(bz) == 0 {
Expand Down Expand Up @@ -228,6 +222,9 @@ func (k *Keeper) iterateMetadata(ctx sdk.Context, cb func(clientID string, key,
func (k *Keeper) GetAllGenesisClients(ctx sdk.Context) types.IdentifiedClientStates {
var genClients types.IdentifiedClientStates
k.IterateClientStates(ctx, nil, func(clientID string, cs exported.ClientState) bool {
if clientID == exported.LocalhostClientID {
return false
}
genClients = append(genClients, types.NewIdentifiedClientState(clientID, cs))
return false
})
Expand Down Expand Up @@ -355,6 +352,10 @@ func (k *Keeper) IterateClientStates(ctx sdk.Context, storePrefix []byte, cb fun
store := ctx.KVStore(k.storeKey)
iterator := storetypes.KVStorePrefixIterator(store, host.PrefixedClientStoreKey(storePrefix))

if cb(exported.LocalhostClientID, localhost.NewClientState(types.GetSelfHeight(ctx))) {
return
}
colin-axner marked this conversation as resolved.
Show resolved Hide resolved

defer sdk.LogDeferred(ctx.Logger(), func() error { return iterator.Close() })
for ; iterator.Valid(); iterator.Next() {
path := string(iterator.Key())
Expand Down
10 changes: 5 additions & 5 deletions modules/core/02-client/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ import (
host "github.com/cosmos/ibc-go/v8/modules/core/24-host"
"github.com/cosmos/ibc-go/v8/modules/core/exported"
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"
"github.com/cosmos/ibc-go/v8/testing/simapp"
)
Expand Down Expand Up @@ -143,10 +142,9 @@ func (suite *KeeperTestSuite) TestSetClientConsensusState() {

func (suite *KeeperTestSuite) TestGetAllGenesisClients() {
clientIDs := []string{
exported.LocalhostClientID, testClientID2, testClientID3, testClientID,
testClientID2, testClientID3, testClientID,
}
expClients := []exported.ClientState{
localhost.NewClientState(types.GetSelfHeight(suite.chainA.GetContext())),
ibctm.NewClientState(testChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.ZeroHeight(), commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath),
ibctm.NewClientState(testChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.ZeroHeight(), commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath),
ibctm.NewClientState(testChainID, ibctm.DefaultTrustLevel, trustingPeriod, ubdPeriod, maxClockDrift, types.ZeroHeight(), commitmenttypes.GetSDKSpecs(), ibctesting.UpgradePath),
Expand Down Expand Up @@ -315,14 +313,16 @@ func (suite *KeeperTestSuite) TestIterateClientStates() {
"tendermint clientIDs",
[]byte(exported.Tendermint),
func() []string {
return expTMClientIDs
clientIDs := []string{exported.LocalhostClientID}
return append(clientIDs, expTMClientIDs...)
},
},
{
"solo machine clientIDs",
[]byte(exported.Solomachine),
func() []string {
return expSMClientIDs
clientIDs := []string{exported.LocalhostClientID}
return append(clientIDs, expSMClientIDs...)
},
},
}
Expand Down
5 changes: 5 additions & 0 deletions modules/core/02-client/keeper/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v7"
"github.com/cosmos/ibc-go/v8/modules/core/02-client/migrations/v9"
"github.com/cosmos/ibc-go/v8/modules/core/02-client/types"
)

Expand Down Expand Up @@ -47,3 +48,7 @@ func (m Migrator) MigrateParams(ctx sdk.Context) error {
m.keeper.Logger(ctx).Info("successfully migrated client to self-manage params")
return nil
}

func (m Migrator) Migrate6to7(ctx sdk.Context) error {
return v9.MigrateStore(ctx, m.keeper)
}
colin-axner marked this conversation as resolved.
Show resolved Hide resolved
1 change: 0 additions & 1 deletion modules/core/02-client/migrations/v7/expected_keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,4 @@ type ClientKeeper interface {
GetClientState(ctx sdk.Context, clientID string) (exported.ClientState, bool)
SetClientState(ctx sdk.Context, clientID string, clientState exported.ClientState)
ClientStore(ctx sdk.Context, clientID string) storetypes.KVStore
CreateLocalhostClient(ctx sdk.Context) error
DimitrisJim marked this conversation as resolved.
Show resolved Hide resolved
}
Loading
Loading