diff --git a/proto/interchain_security/ccv/consumer/v1/genesis.proto b/proto/interchain_security/ccv/consumer/v1/genesis.proto index 034ff68372..eb83598e6f 100644 --- a/proto/interchain_security/ccv/consumer/v1/genesis.proto +++ b/proto/interchain_security/ccv/consumer/v1/genesis.proto @@ -29,11 +29,15 @@ message GenesisState { // HeightToValsetUpdateId nil on new chain, filled in on restart. repeated HeightToValsetUpdateID height_to_valset_update_id = 9 [ (gogoproto.nullable) = false ]; - // OutstandingDowntimes nil on new chain, filled in on restart. + // OutstandingDowntimes nil on new chain, filled in on restart. repeated OutstandingDowntime outstanding_downtime_slashing = 10 [ (gogoproto.nullable) = false ]; - // PendingConsumerPackets nil on new chain, filled in on restart. - ConsumerPackets pending_consumer_packets = 11; + // PendingConsumerPackets nil on new chain, filled in on restart. + ConsumerPackets pending_consumer_packets = 11 + [ (gogoproto.nullable) = false ]; + // LastTransmissionBlockHeight nil on new chain, filled in on restart. + interchain_security.ccv.consumer.v1.LastTransmissionBlockHeight last_transmission_block_height = 12 + [ (gogoproto.nullable) = false ]; } // MaturingVSCPacket defines the genesis information for the diff --git a/x/ccv/consumer/keeper/genesis.go b/x/ccv/consumer/keeper/genesis.go index 0166fa08b6..fbbe42fa17 100644 --- a/x/ccv/consumer/keeper/genesis.go +++ b/x/ccv/consumer/keeper/genesis.go @@ -54,18 +54,16 @@ func (k Keeper) InitGenesis(ctx sdk.Context, state *consumertypes.GenesisState) // set default value for valset update ID k.SetHeightValsetUpdateID(ctx, uint64(ctx.BlockHeight()), uint64(0)) + } else { // verify genesis initial valset against the latest consensus state // IBC genesis MUST run before CCV consumer genesis if err := k.verifyGenesisInitValset(ctx, state); err != nil { panic(err) } - // chain restarts without the CCV channel established - if state.ProviderChannelId == "" { - k.AppendPendingPacket(ctx, state.PendingConsumerPackets.List...) - // chain restarts with the CCV channel established - } else { + // chain restarts with the CCV channel established + if state.ProviderChannelId != "" { // set provider channel ID k.SetProviderChannel(ctx, state.ProviderChannelId) // set all unbonding sequences @@ -80,6 +78,7 @@ func (k Keeper) InitGenesis(ctx sdk.Context, state *consumertypes.GenesisState) } k.SetOutstandingDowntime(ctx, consAddr) } + // set last transmission block height err := k.SetLastTransmissionBlockHeight(ctx, state.LastTransmissionBlockHeight) if err != nil { @@ -88,7 +87,9 @@ func (k Keeper) InitGenesis(ctx sdk.Context, state *consumertypes.GenesisState) } - // set pending packet + // set pending consumer pending packets + // note that genesis states embed defined pending mature VSC packets only if the handshake is completed + k.AppendPendingPacket(ctx, state.PendingConsumerPackets.List...) // set height to valset update id mapping for _, h2v := range state.HeightToValsetUpdateId { diff --git a/x/ccv/consumer/keeper/genesis_test.go b/x/ccv/consumer/keeper/genesis_test.go index de38b747b9..57c1aac49f 100644 --- a/x/ccv/consumer/keeper/genesis_test.go +++ b/x/ccv/consumer/keeper/genesis_test.go @@ -74,7 +74,8 @@ func TestInitGenesis(t *testing.T) { List: []consumertypes.ConsumerPacket{ { Type: consumertypes.SlashPacket, - Data: ccv.NewSlashPacketData(abciValidator, vscID, stakingtypes.Downtime).GetBytes()}, + Data: ccv.NewSlashPacketData(abciValidator, vscID, stakingtypes.Downtime).GetBytes(), + }, { Type: consumertypes.VscMaturedPacket, Data: ccv.NewVSCMaturedPacketData(1).GetBytes(), diff --git a/x/ccv/consumer/keeper/keeper.go b/x/ccv/consumer/keeper/keeper.go index 8749414097..0578b2726c 100644 --- a/x/ccv/consumer/keeper/keeper.go +++ b/x/ccv/consumer/keeper/keeper.go @@ -429,9 +429,9 @@ func (k Keeper) DeletePendingDataPackets(ctx sdk.Context) { } // AppendPendingDataPacket appends the given data packet to the pending data packets in store -func (k Keeper) AppendPendingPacket(ctx sdk.Context, packet types.ConsumerPacket) { +func (k Keeper) AppendPendingPacket(ctx sdk.Context, packet ...types.ConsumerPacket) { pending := k.GetPendingPackets(ctx) - list := append(pending.GetList(), packet) + list := append(pending.GetList(), packet...) k.SetPendingPackets(ctx, types.ConsumerPackets{List: list}) } diff --git a/x/ccv/consumer/types/genesis.go b/x/ccv/consumer/types/genesis.go index cf2a770012..89ab381920 100644 --- a/x/ccv/consumer/types/genesis.go +++ b/x/ccv/consumer/types/genesis.go @@ -30,7 +30,7 @@ func NewRestartGenesisState( maturingPackets []MaturingVSCPacket, initValSet []abci.ValidatorUpdate, heightToValsetUpdateIDs []HeightToValsetUpdateID, - pendingSlashRequests SlashRequests, + pendingConsumerPackets ConsumerPackets, outstandingDowntimes []OutstandingDowntime, lastTransBlockHeight LastTransmissionBlockHeight, params Params, @@ -44,7 +44,7 @@ func NewRestartGenesisState( NewChain: false, InitialValSet: initValSet, HeightToValsetUpdateId: heightToValsetUpdateIDs, - PendingSlashRequests: pendingSlashRequests, + PendingConsumerPackets: pendingConsumerPackets, OutstandingDowntimeSlashing: outstandingDowntimes, LastTransmissionBlockHeight: lastTransBlockHeight, } @@ -108,6 +108,9 @@ func (gs GenesisState) Validate() error { if len(gs.MaturingPackets) != 0 { return sdkerrors.Wrap(ccv.ErrInvalidGenesis, "maturing packets must be empty for new chain") } + if len(gs.PendingConsumerPackets.List) != 0 { + return sdkerrors.Wrap(ccv.ErrInvalidGenesis, "pending consumer packets must be empty for new chain") + } if gs.LastTransmissionBlockHeight.Height != 0 { return sdkerrors.Wrap(ccv.ErrInvalidGenesis, "last transmission block height must be empty for new chain") } @@ -142,6 +145,13 @@ func (gs GenesisState) Validate() error { return sdkerrors.Wrap( ccv.ErrInvalidGenesis, "last transmission block height must be zero when handshake isn't completed") } + if len(gs.PendingConsumerPackets.List) != 0 { + for _, packet := range gs.PendingConsumerPackets.List { + if packet.Type == VscMaturedPacket { + return sdkerrors.Wrap(ccv.ErrInvalidGenesis, "pending maturing packets must be empty for new chain") + } + } + } } if gs.HeightToValsetUpdateId == nil { return sdkerrors.Wrap( diff --git a/x/ccv/consumer/types/genesis_test.go b/x/ccv/consumer/types/genesis_test.go index 21f8429b28..c074cba70e 100644 --- a/x/ccv/consumer/types/genesis_test.go +++ b/x/ccv/consumer/types/genesis_test.go @@ -4,9 +4,13 @@ import ( "testing" "time" + sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types" commitmenttypes "github.com/cosmos/ibc-go/v3/modules/core/23-commitment/types" ibctmtypes "github.com/cosmos/ibc-go/v3/modules/light-clients/07-tendermint/types" + abci "github.com/tendermint/tendermint/abci/types" "github.com/cosmos/interchain-security/x/ccv/consumer/types" @@ -14,6 +18,7 @@ import ( testutil "github.com/cosmos/interchain-security/testutil/keeper" + ccv "github.com/cosmos/interchain-security/x/ccv/types" "github.com/stretchr/testify/require" ) @@ -152,6 +157,24 @@ func TestValidateInitialGenesisState(t *testing.T) { }, true, }, + { + "invalid new consumer genesis state: non-empty pending consumer packets", + &types.GenesisState{ + params, + "", + "", + true, + cs, + consensusState, + nil, + valUpdates, + nil, + nil, + types.ConsumerPackets{List: []types.ConsumerPacket{{}}}, + types.LastTransmissionBlockHeight{}, + }, + true, + }, { "invalid new consumer genesis state: nil initial validator set", types.NewInitialGenesisState(cs, consensusState, nil, params), @@ -173,6 +196,14 @@ func TestValidateInitialGenesisState(t *testing.T) { valUpdates, params), true, }, + { + "invalid new consumer genesis state: initial validator set does not match validator set hash", + types.NewInitialGenesisState( + cs, ibctmtypes.NewConsensusState( + time.Now(), commitmenttypes.NewMerkleRoot([]byte("apphash")), []byte("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08")), + valUpdates, params), + true, + }, { "invalid new consumer genesis state: invalid params", types.NewInitialGenesisState(cs, consensusState, valUpdates, @@ -214,6 +245,19 @@ func TestValidateRestartGenesisState(t *testing.T) { valHash := valSet.Hash() valUpdates := tmtypes.TM2PB.ValidatorUpdates(valSet) + matConsumerPacket := types.ConsumerPacket{ + Type: types.VscMaturedPacket, + Data: ccv.NewVSCMaturedPacketData(1).GetBytes(), + } + + slashConsumerPacket := types.ConsumerPacket{ + Type: types.SlashPacket, + Data: ccv.NewSlashPacketData( + abci.Validator{Address: pubKey.Address(), Power: int64(1)}, + 1, + stakingtypes.Downtime).GetBytes(), + } + // create default height to validator set update id mapping heightToValsetUpdateID := []types.HeightToValsetUpdateID{ {Height: 0, ValsetUpdateId: 0}, @@ -232,7 +276,14 @@ func TestValidateRestartGenesisState(t *testing.T) { }{ { "valid restart consumer genesis state: empty maturing packets", - types.NewRestartGenesisState("ccvclient", "ccvchannel", nil, valUpdates, heightToValsetUpdateID, types.SlashRequests{}, nil, types.LastTransmissionBlockHeight{}, params), + types.NewRestartGenesisState("ccvclient", "ccvchannel", nil, valUpdates, heightToValsetUpdateID, + types.ConsumerPackets{List: []types.ConsumerPacket{matConsumerPacket, slashConsumerPacket}}, nil, types.LastTransmissionBlockHeight{Height: 100}, params), + false, + }, + { + "valid restart consumer genesis state: handshake in progress ", + types.NewRestartGenesisState("ccvclient", "", nil, valUpdates, heightToValsetUpdateID, + types.ConsumerPackets{List: []types.ConsumerPacket{slashConsumerPacket}}, nil, types.LastTransmissionBlockHeight{}, params), false, }, { @@ -241,26 +292,28 @@ func TestValidateRestartGenesisState(t *testing.T) { {1, uint64(time.Now().UnixNano())}, {3, uint64(time.Now().UnixNano())}, {5, uint64(time.Now().UnixNano())}, - }, valUpdates, heightToValsetUpdateID, types.SlashRequests{}, nil, types.LastTransmissionBlockHeight{}, params), + }, valUpdates, heightToValsetUpdateID, types.ConsumerPackets{}, + []types.OutstandingDowntime{{ValidatorConsensusAddress: sdk.ConsAddress(validator.Address.Bytes()).String()}}, + types.LastTransmissionBlockHeight{}, params), false, }, { "invalid restart consumer genesis state: provider id is empty", - types.NewRestartGenesisState("", "ccvchannel", nil, valUpdates, heightToValsetUpdateID, types.SlashRequests{}, nil, types.LastTransmissionBlockHeight{}, params), + types.NewRestartGenesisState("", "ccvchannel", nil, valUpdates, heightToValsetUpdateID, types.ConsumerPackets{}, nil, types.LastTransmissionBlockHeight{}, params), true, }, { "invalid restart consumer genesis state: maturing packet vscId is invalid", types.NewRestartGenesisState("ccvclient", "ccvchannel", []types.MaturingVSCPacket{ {0, uint64(time.Now().UnixNano())}, - }, valUpdates, nil, types.SlashRequests{}, nil, types.LastTransmissionBlockHeight{}, params), + }, valUpdates, nil, types.ConsumerPackets{}, nil, types.LastTransmissionBlockHeight{}, params), true, }, { "invalid restart consumer genesis state: maturing packet time is invalid", types.NewRestartGenesisState("ccvclient", "ccvchannel", []types.MaturingVSCPacket{ {1, 0}, - }, valUpdates, nil, types.SlashRequests{}, nil, types.LastTransmissionBlockHeight{}, params), + }, valUpdates, nil, types.ConsumerPackets{}, nil, types.LastTransmissionBlockHeight{}, params), true, }, { @@ -306,7 +359,7 @@ func TestValidateRestartGenesisState(t *testing.T) { }, { "invalid restart consumer genesis state: nil height to validator set id mapping", - types.NewRestartGenesisState("ccvclient", "",ConsumerPackets + types.NewRestartGenesisState("ccvclient", "", []types.MaturingVSCPacket{{1, 0}}, valUpdates, nil, types.ConsumerPackets{}, nil, types.LastTransmissionBlockHeight{}, params), true, }, { @@ -328,6 +381,20 @@ func TestValidateRestartGenesisState(t *testing.T) { nil, valUpdates, heightToValsetUpdateID, types.ConsumerPackets{}, nil, types.LastTransmissionBlockHeight{Height: int64(1)}, params), true, }, + { + "invalid restart consumer genesis state: pending maturing packets defined when handshake is still in progress", + types.NewRestartGenesisState("ccvclient", "", + nil, valUpdates, heightToValsetUpdateID, types.ConsumerPackets{ + List: []types.ConsumerPacket{ + + { + Type: types.VscMaturedPacket, + Data: ccv.NewVSCMaturedPacketData(1).GetBytes(), + }, + }, + }, nil, types.LastTransmissionBlockHeight{Height: int64(1)}, params), + true, + }, { "invalid restart consumer genesis state: invalid params", types.NewRestartGenesisState("ccvclient", "ccvchannel", nil, valUpdates, nil, types.ConsumerPackets{}, nil, types.LastTransmissionBlockHeight{}, diff --git a/x/ccv/provider/types/genesis.pb.go b/x/ccv/provider/types/genesis.pb.go index ae96c161a0..f092530348 100644 --- a/x/ccv/provider/types/genesis.pb.go +++ b/x/ccv/provider/types/genesis.pb.go @@ -135,7 +135,7 @@ func (m *GenesisState) GetParams() Params { // consumer chain type ConsumerState struct { - // ChainlID defines the chain ID for the consumer chain + // ChainID defines the chain ID for the consumer chain ChainId string `protobuf:"bytes,1,opt,name=chain_id,json=chainId,proto3" json:"chain_id,omitempty"` // ChannelID defines the IBC channel ID for the consumer chain ChannelId string `protobuf:"bytes,2,opt,name=channel_id,json=channelId,proto3" json:"channel_id,omitempty"`