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

Create Parent Proposal and use gov-created client in Handshake #5

Merged
merged 11 commits into from
Nov 11, 2021
27 changes: 15 additions & 12 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import (
upgradeclient "github.com/cosmos/cosmos-sdk/x/upgrade/client"
upgradekeeper "github.com/cosmos/cosmos-sdk/x/upgrade/keeper"
upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types"

"github.com/cosmos/ibc-go/modules/apps/transfer"
ibctransferkeeper "github.com/cosmos/ibc-go/modules/apps/transfer/keeper"
ibctransfertypes "github.com/cosmos/ibc-go/modules/apps/transfer/types"
Expand All @@ -77,7 +78,9 @@ import (
ibchost "github.com/cosmos/ibc-go/modules/core/24-host"
ibckeeper "github.com/cosmos/ibc-go/modules/core/keeper"
ibctesting "github.com/cosmos/ibc-go/testing"

"github.com/spf13/cast"

abci "github.com/tendermint/tendermint/abci/types"
tmjson "github.com/tendermint/tendermint/libs/json"
"github.com/tendermint/tendermint/libs/log"
Expand All @@ -90,6 +93,7 @@ import (
ibcparent "github.com/cosmos/interchain-security/x/ccv/parent"
ibcparentkeeper "github.com/cosmos/interchain-security/x/ccv/parent/keeper"
ibcparenttypes "github.com/cosmos/interchain-security/x/ccv/parent/types"
ccv "github.com/cosmos/interchain-security/x/ccv/types"

"github.com/tendermint/spm/cosmoscmd"
"github.com/tendermint/spm/openapiconsole"
Expand Down Expand Up @@ -324,13 +328,23 @@ func New(
appCodec, keys[ibchost.StoreKey], app.GetSubspace(ibchost.ModuleName), app.StakingKeeper, app.UpgradeKeeper, scopedIBCKeeper,
)

// Create CCV child and parent keepers and modules
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume the chain created in this repo is able to be both parent and child, correct?

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct, though I believe the default genesis disables the child module.

app.ChildKeeper = ibcchildkeeper.NewKeeper(appCodec, keys[ibcchildtypes.StoreKey], scopedIBCChildKeeper,
app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, app.IBCKeeper.ConnectionKeeper, app.IBCKeeper.ClientKeeper,
)
childModule := ibcchild.NewAppModule(app.ChildKeeper)
app.ParentKeeper = ibcparentkeeper.NewKeeper(appCodec, keys[ibcparenttypes.StoreKey], scopedIBCParentKeeper,
app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, app.IBCKeeper.ConnectionKeeper, app.IBCKeeper.ClientKeeper, nil)
parentModule := ibcparent.NewAppModule(app.ParentKeeper)

// register the proposal types
govRouter := govtypes.NewRouter()
govRouter.AddRoute(govtypes.RouterKey, govtypes.ProposalHandler).
AddRoute(paramproposal.RouterKey, params.NewParamChangeProposalHandler(app.ParamsKeeper)).
AddRoute(distrtypes.RouterKey, distr.NewCommunityPoolSpendProposalHandler(app.DistrKeeper)).
AddRoute(upgradetypes.RouterKey, upgrade.NewSoftwareUpgradeProposalHandler(app.UpgradeKeeper)).
AddRoute(ibchost.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper))
AddRoute(ibchost.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)).
AddRoute(ccv.RouterKey, ibcparent.NewCreateChildChainHandler(app.ParentKeeper))

// Create Transfer Keepers
app.TransferKeeper = ibctransferkeeper.NewKeeper(
Expand All @@ -352,17 +366,6 @@ func New(
&stakingKeeper, govRouter,
)

// Create CCV child and parent keepers and modules
app.ChildKeeper = ibcchildkeeper.NewKeeper(appCodec, keys[ibcchildtypes.StoreKey], scopedIBCChildKeeper,
app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, app.IBCKeeper.ConnectionKeeper, app.IBCKeeper.ClientKeeper,
)
childModule := ibcchild.NewAppModule(app.ChildKeeper)
app.ParentKeeper = ibcparentkeeper.NewKeeper(appCodec, keys[ibcparenttypes.StoreKey], scopedIBCParentKeeper,
app.IBCKeeper.ChannelKeeper, &app.IBCKeeper.PortKeeper, app.IBCKeeper.ConnectionKeeper, app.IBCKeeper.ClientKeeper, nil)
parentModule := ibcparent.NewAppModule(app.ParentKeeper)

// this line is used by starport scaffolding # stargate/app/keeperDefinition

// Create static IBC router, add transfer route, then set and seal it
ibcRouter := ibcporttypes.NewRouter()
ibcRouter.AddRoute(ibctransfertypes.ModuleName, transferModule)
Expand Down
6 changes: 2 additions & 4 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@ require (
github.com/cosmos/cosmos-sdk v0.44.0
github.com/cosmos/ibc-go v1.2.0
github.com/gogo/protobuf v1.3.3
github.com/golang/protobuf v1.5.2 // indirect
github.com/golang/protobuf v1.5.2
github.com/google/go-cmp v0.5.6 // indirect
github.com/gorilla/mux v1.8.0
github.com/grpc-ecosystem/grpc-gateway v1.16.0
github.com/grpc-ecosystem/grpc-gateway/v2 v2.6.0 // indirect
github.com/regen-network/cosmos-proto v0.3.1 // indirect
github.com/spf13/cast v1.3.1
github.com/spf13/cobra v1.1.3
github.com/stretchr/testify v1.7.0
Expand All @@ -20,7 +18,7 @@ require (
github.com/tendermint/tm-db v0.6.4
google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83
google.golang.org/grpc v1.40.0
google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.0.0 // indirect
google.golang.org/protobuf v1.27.1
)

replace (
Expand Down
290 changes: 0 additions & 290 deletions go.sum

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions proto/interchain_security/ccv/v1/ccv.proto
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ option go_package = "github.com/cosmos/interchain-security/x/ccv/types";

import "gogoproto/gogo.proto";
import "tendermint/abci/types.proto";
import "google/protobuf/any.proto";
import "google/protobuf/timestamp.proto";

// This packet is sent from parent chain to baby chain if the validator set for baby chain
// changes (due to new bonding/unbonding messages or slashing events)
Expand All @@ -30,3 +32,28 @@ enum Status {
// channel is invalid and can no longer process packets
STATUS_INVALID = 3 [(gogoproto.enumvalue_customname) = "INVALID"];
}

// CreateChildChainProposal is a governance proposal on the parent chain to spawn a new child chain.
// If it passes, then all validators on the parent chain are expected to validate the child chain at spawn time
// or get slashed. It is recommended that spawn time occurs after the proposal end time.
message CreateChildChainProposal {
option (gogoproto.goproto_getters) = false;
option (gogoproto.goproto_stringer) = false;

// the title of the proposal
string title = 1;
// the description of the proposal
string description = 2;
// the proposed chain-id of the new child chain, must be different from all other child chain ids of the executing
// parent chain.
string chain_id = 3 [(gogoproto.moretags) = "yaml:\"chain_id\""];
// the proposed client state of new child chain.
// Since it contains no consensus information, this may be created before child chain is created.
google.protobuf.Any client_state = 4 [(gogoproto.moretags) = "yaml:\"client_state\""];
// genesis hash with no staking information included.
Copy link
Contributor

Choose a reason for hiding this comment

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

with no staking information included

i.e without the genesis module included? We should also specify in the comment that this is the child chain's genesis hash

Copy link
Member Author

Choose a reason for hiding this comment

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

Correct. Basically without any gen_txs or staking genesis. Since that will be informed by the parent chain

bytes genesis_hash = 5 [(gogoproto.moretags) = "yaml:\"genesis_hash\""];
// spawn time is the time on the parent chain at which the child chain genesis is finalized and all validators
// will be responsible for starting their child chain validator node.
google.protobuf.Timestamp spawn_time = 6
[(gogoproto.moretags) = "yaml:\"spawn_time\"", (gogoproto.stdtime) = true, (gogoproto.nullable) = false];
}
6 changes: 4 additions & 2 deletions x/ccv/child/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,13 @@ func (suite *KeeperTestSuite) SetupTest() {
}
// set child endpoint's clientID
suite.path.EndpointA.ClientID = parentClient

// create child client on parent chain and set as child client for child chainID in parent keeper.
suite.path.EndpointB.CreateClient()
suite.parentChain.App.(*app.App).ParentKeeper.SetChildClient(suite.parentChain.GetContext(), suite.childChain.ChainID, suite.path.EndpointB.ClientID)
}

func (suite *KeeperTestSuite) SetupCCVChannel() {
// create child client on parent chain
suite.path.EndpointB.CreateClient()
suite.coordinator.CreateConnections(suite.path)
suite.coordinator.CreateChannels(suite.path)
}
Expand Down
3 changes: 2 additions & 1 deletion x/ccv/child/module_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,9 @@ func (suite *ChildTestSuite) SetupTest() {
// set child endpoint's clientID
path.EndpointA.ClientID = parentClient

// create child client on parent chain
// create child client on parent chain and set as child client for child chainID in parent keeper.
path.EndpointB.CreateClient()
suite.parentChain.App.(*app.App).ParentKeeper.SetChildClient(suite.parentChain.GetContext(), suite.childChain.ChainID, path.EndpointB.ClientID)

suite.coordinator.CreateConnections(path)

Expand Down
2 changes: 1 addition & 1 deletion x/ccv/parent/keeper/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (k Keeper) ExportGenesis(ctx sdk.Context) *types.ParentGenesisState {
var childStates []types.ChildState

for ; iterator.Valid(); iterator.Next() {
channelID := string(iterator.Key())
channelID := string(iterator.Key()[len(parenttypes.ChannelToChainKeyPrefix+"/"):])
chainID := string(iterator.Value())

status := k.GetChannelStatus(ctx, channelID)
Expand Down
8 changes: 4 additions & 4 deletions x/ccv/parent/keeper/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ func (suite *KeeperTestSuite) TestGenesis() {
// set some chain-channel pairs before exporting
ctx := suite.parentChain.GetContext()
for i := 0; i < 4; i++ {
suite.parentChain.App.(*app.App).ParentKeeper.SetChainToChannel(ctx, fmt.Sprintf("chainid-%d", i), fmt.Sprintf("channel-%d", i))
suite.parentChain.App.(*app.App).ParentKeeper.SetChannelToChain(ctx, fmt.Sprintf("channel-%d", i), fmt.Sprintf("chainid-%d", i))
suite.parentChain.App.(*app.App).ParentKeeper.SetChannelStatus(ctx, fmt.Sprintf("channel-%d", i), types.Status(i))
suite.parentChain.App.(*app.App).ParentKeeper.SetChainToChannel(ctx, fmt.Sprintf("chainid-%d", i), fmt.Sprintf("channelid-%d", i))
AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved
suite.parentChain.App.(*app.App).ParentKeeper.SetChannelToChain(ctx, fmt.Sprintf("channelid-%d", i), fmt.Sprintf("chainid-%d", i))
suite.parentChain.App.(*app.App).ParentKeeper.SetChannelStatus(ctx, fmt.Sprintf("channelid-%d", i), types.Status(i))
}

genState := suite.parentChain.App.(*app.App).ParentKeeper.ExportGenesis(suite.parentChain.GetContext())
Expand All @@ -32,6 +32,6 @@ func (suite *KeeperTestSuite) TestGenesis() {
suite.Require().Equal(expectedChannelId, channelID, "did not store correct channel id for given chain id")

status := suite.childChain.App.(*app.App).ParentKeeper.GetChannelStatus(ctx, channelID)
suite.Require().Equal(int32(i), status, "status is unexpected for given channel id: %s", channelID)
suite.Require().Equal(types.Status(i), status, "status is unexpected for given channel id: %s", channelID)
}
}
7 changes: 5 additions & 2 deletions x/ccv/parent/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -188,11 +188,14 @@ func (k Keeper) VerifyChildChain(ctx sdk.Context, channelID string) error {
if status != ccv.INITIALIZING {
return sdkerrors.Wrap(ccv.ErrInvalidStatus, "CCV channel status must be in Initializing state")
}
_, tmClient, err := k.getUnderlyingClient(ctx, channelID)
clientID, tmClient, err := k.getUnderlyingClient(ctx, channelID)
if err != nil {
return err
}
// TODO: Verify consensus state against initial validator set of the child chain
ccvClientId := k.GetChildClient(ctx, tmClient.ChainId)
if ccvClientId != clientID {
return sdkerrors.Wrapf(ccv.ErrInvalidChildClient, "CCV channel must be built on top of CCV client. expected %s, got %s", ccvClientId, clientID)
}

// Verify that there isn't already a CCV channel for the child chain
if prevChannel, ok := k.GetChainToChannel(ctx, tmClient.ChainId); ok {
Expand Down
13 changes: 13 additions & 0 deletions x/ccv/parent/keeper/keeper_test.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
package keeper_test

import (
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"

clienttypes "github.com/cosmos/ibc-go/modules/core/02-client/types"
channeltypes "github.com/cosmos/ibc-go/modules/core/04-channel/types"
commitmenttypes "github.com/cosmos/ibc-go/modules/core/23-commitment/types"
ibctmtypes "github.com/cosmos/ibc-go/modules/light-clients/07-tendermint/types"
ibctesting "github.com/cosmos/ibc-go/testing"

"github.com/cosmos/interchain-security/app"
"github.com/cosmos/interchain-security/testutil/simapp"
childtypes "github.com/cosmos/interchain-security/x/ccv/child/types"
parenttypes "github.com/cosmos/interchain-security/x/ccv/parent/types"
"github.com/cosmos/interchain-security/x/ccv/types"

"github.com/stretchr/testify/suite"
)

Expand Down Expand Up @@ -74,4 +79,12 @@ func (suite *KeeperTestSuite) SetupTest() {
}
// set child endpoint's clientID
suite.path.EndpointA.ClientID = parentClient

// create child client on parent chain and set as child client for child chainID in parent keeper.
suite.path.EndpointB.CreateClient()
suite.parentChain.App.(*app.App).ParentKeeper.SetChildClient(suite.parentChain.GetContext(), suite.childChain.ChainID, suite.path.EndpointB.ClientID)
}

func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}
111 changes: 111 additions & 0 deletions x/ccv/parent/keeper/proposal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
package keeper

import (
"encoding/binary"
"strings"
"time"

sdk "github.com/cosmos/cosmos-sdk/types"
clienttypes "github.com/cosmos/ibc-go/modules/core/02-client/types"
commitmenttypes "github.com/cosmos/ibc-go/modules/core/23-commitment/types"
"github.com/cosmos/ibc-go/modules/core/exported"
ibcexported "github.com/cosmos/ibc-go/modules/core/exported"
ibctmtypes "github.com/cosmos/ibc-go/modules/light-clients/07-tendermint/types"
"github.com/cosmos/interchain-security/x/ccv/parent/types"
ccv "github.com/cosmos/interchain-security/x/ccv/types"
)

// CreateChildChainProposal will receive the child chain's client state from the proposal.
// If the spawn time has already passed, then set the child chain. Otherwise store the client
// as a pending client, and set once spawn time has passed.
func (k Keeper) CreateChildChainProposal(ctx sdk.Context, p *ccv.CreateChildChainProposal) error {
clientState, err := clienttypes.UnpackClientState(p.ClientState)
if err != nil {
return err
}
if ctx.BlockTime().After(p.SpawnTime) {
err = k.CreateChildClient(ctx, p.ChainId, clientState)
if err != nil {
return err
}
return nil
AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved
}

k.SetPendingClient(ctx, p.SpawnTime, p.ChainId, clientState)
return nil
}

// CreateChildClient will create the CCV client for the given child chain. The CCV channel must be built
// on top of the CCV client to ensure connection with the right child chain.
func (k Keeper) CreateChildClient(ctx sdk.Context, chainID string, clientState ibcexported.ClientState) error {
// TODO: Allow for current validators to set different keys
consensusState := ibctmtypes.NewConsensusState(ctx.BlockTime(), commitmenttypes.NewMerkleRoot([]byte(ibctmtypes.SentinelRoot)), ctx.BlockHeader().NextValidatorsHash)
clientID, err := k.clientKeeper.CreateClient(ctx, clientState, consensusState)
if err != nil {
return err
}
k.SetChildClient(ctx, chainID, clientID)
return nil
}

// SetChildClient sets the clientID for the given chainID
func (k Keeper) SetChildClient(ctx sdk.Context, chainID, clientID string) {
store := ctx.KVStore(k.storeKey)
store.Set(types.ChainToClientKey(chainID), []byte(clientID))
}

// GetChildClient returns the clientID for the given chainID.
func (k Keeper) GetChildClient(ctx sdk.Context, chainID string) string {
store := ctx.KVStore(k.storeKey)
return string(store.Get(types.ChainToClientKey(chainID)))
}

// SetPendingClient sets an IdentifiedClient for the given timestamp
func (k Keeper) SetPendingClient(ctx sdk.Context, timestamp time.Time, chainID string, clientState ibcexported.ClientState) error {
store := ctx.KVStore(k.storeKey)
bz, err := k.cdc.MarshalInterface(clientState)
if err != nil {
return err
}
store.Set(types.PendingClientKey(timestamp, chainID), bz)
return nil
}

// GetPendingClient gets an IdentifiedClient for the given timestamp
func (k Keeper) GetPendingClient(ctx sdk.Context, timestamp time.Time, chainID string) ibcexported.ClientState {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.PendingClientKey(timestamp, chainID))
if bz == nil {
AdityaSripal marked this conversation as resolved.
Show resolved Hide resolved
return nil
}
return clienttypes.MustUnmarshalClientState(k.cdc, bz)
}

// IteratePendingClients iterates over the pending clients in order and sets the child client if the spawn time has passed,
// otherwise it will break out of loop and return.
func (k Keeper) IteratePendingClients(ctx sdk.Context) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, []byte(types.PendingClientKeyPrefix+"/"))
defer iterator.Close()

if !iterator.Valid() {
return
}

for ; iterator.Valid(); iterator.Next() {
suffixKey := iterator.Key()
// splitKey contains the bigendian time in the first element and the chainID in the second element/
splitKey := strings.Split(string(suffixKey), "/")

timeNano := binary.BigEndian.Uint64([]byte(splitKey[0]))
spawnTime := time.Unix(0, int64(timeNano))
var cs exported.ClientState
k.cdc.UnmarshalInterface(iterator.Value(), cs)

if ctx.BlockTime().After(spawnTime) {
k.CreateChildClient(ctx, splitKey[1], cs)
} else {
break
}
}
}
Loading