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!: store proposed chainID before voting finishes #1289

Merged
merged 38 commits into from
Oct 2, 2023
Merged
Show file tree
Hide file tree
Changes from 32 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ac7e1b9
feat: store chain in proposal
Sep 12, 2023
0eea515
add govHook
Sep 12, 2023
a79f384
delete GetChainsInProposal
Sep 14, 2023
690c61c
check proposal type
Sep 14, 2023
658b5f5
update key
Sep 15, 2023
b0a3721
feat: add query proposed chainIDs
Sep 19, 2023
fbb74fc
feat: set govhook
Sep 19, 2023
b61bd85
feat: parse key
Sep 19, 2023
d13c124
refactor: names
Sep 19, 2023
1c6d099
feat: add list proposed consumer chains
Sep 20, 2023
2f007f5
test: add e2e test
Sep 20, 2023
402fcd4
merge main
Sep 20, 2023
27daf3a
add e2e test
Sep 20, 2023
114db15
update comments
Sep 20, 2023
74b7d91
update ProposeConsumerChains in e2e test
Sep 21, 2023
8db40a8
remove wait for block
Sep 21, 2023
5a26420
docs: update changelog
Sep 21, 2023
956b4cc
fix: lint
Sep 21, 2023
f193361
add TestParseProposedConsumerChainKey
Sep 21, 2023
c2cb529
refactor gov hook
Sep 21, 2023
8253ee7
Update proto/interchain_security/ccv/provider/v1/query.proto
yaruwangway Sep 21, 2023
f0c591e
update proto
Sep 21, 2023
4aa274a
add test for set kv
Sep 21, 2023
979b383
refactor key to be prefix_proposalID
Sep 21, 2023
b020e49
formatting
Sep 21, 2023
a90f87c
update e2e test
Sep 21, 2023
c159764
format
Sep 21, 2023
1f2f388
Update x/ccv/provider/keeper/gov_hook.go
yaruwangway Sep 22, 2023
39ca877
Update x/ccv/provider/keeper/keeper.go
yaruwangway Sep 22, 2023
85d0f33
Update x/ccv/provider/keeper/keeper.go
yaruwangway Sep 22, 2023
e094e42
fix e2e test
Sep 22, 2023
c7cda12
fix gosec
Sep 22, 2023
6bf0f39
remove type url check
Oct 2, 2023
dc7371b
test: add unit test
Oct 2, 2023
5ee3116
lint
Oct 2, 2023
d31f4c8
fix lint
Oct 2, 2023
533ea06
fix err
Oct 2, 2023
834a2af
Merge branch 'feat/refactor-key-assignment' into chain-in-proposal
yaruwangway Oct 2, 2023
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Add an entry to the unreleased provider section whenever merging a PR to main th
* (deps) [#1258](https://github.com/cosmos/interchain-security/pull/1258) Bump [cosmos-sdk](https://github.com/cosmos/cosmos-sdk) to [v0.47.4](https://github.com/cosmos/cosmos-sdk/releases/tag/v0.47.4).
* (deps!) [#1196](https://github.com/cosmos/interchain-security/pull/1196) Bump [ibc-go](https://github.com/cosmos/ibc-go) to [v7.2.0](https://github.com/cosmos/ibc-go/releases/tag/v7.2.0).
* `[x/ccv/provider]` (fix) [#1076](https://github.com/cosmos/interchain-security/pull/1076) Add `InitTimeoutTimestamps` and `ExportedVscSendTimestamps` to exported genesis.
* (feature) [#1282](https://github.com/cosmos/interchain-security/issues/1282) In the `ConsumerAdditionProposal`, consumer chainIDs proposed before the voting period finishes are now stored in the state. The gRPC query `/interchain_security/ccv/provider/proposed_consumer_chainids` and CLI command `query provider proposed-consumer-chains` can be used to retrieve this information.

## [Unreleased for Consumer]

Expand Down
9 changes: 7 additions & 2 deletions app/provider/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -443,7 +443,7 @@ func New(
AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper))
govConfig := govtypes.DefaultConfig()

app.GovKeeper = *govkeeper.NewKeeper(
govKeeper := govkeeper.NewKeeper(
appCodec,
keys[govtypes.StoreKey],
app.AccountKeeper,
Expand All @@ -455,7 +455,12 @@ func New(
)

// Set legacy router for backwards compatibility with gov v1beta1
app.GovKeeper.SetLegacyRouter(govRouter)
govKeeper.SetLegacyRouter(govRouter)

govHook := app.ProviderKeeper.GovHooks(govKeeper)
app.GovKeeper = *govKeeper.SetHooks(
govtypes.NewMultiGovHooks(govHook),
)

app.TransferKeeper = ibctransferkeeper.NewKeeper(
appCodec,
Expand Down
21 changes: 21 additions & 0 deletions proto/interchain_security/ccv/provider/v1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ service Query {
option (google.api.http).get =
"/interchain_security/ccv/provider/registered_consumer_reward_denoms";
}

// QueryProposedConsumerChainIDs query chainIDs in consumerAdditionProposals
// before voting period finishes, after voting period finishes, this chainID will be removed from the result
rpc QueryProposedConsumerChainIDs(
QueryProposedChainIDsRequest)
returns (QueryProposedChainIDsResponse) {
option (google.api.http).get =
"/interchain_security/ccv/provider/proposed_consumer_chains";
}
}

message QueryConsumerGenesisRequest { string chain_id = 1; }
Expand Down Expand Up @@ -187,3 +196,15 @@ message QueryRegisteredConsumerRewardDenomsRequest {}
message QueryRegisteredConsumerRewardDenomsResponse {
repeated string denoms = 1;
}

message QueryProposedChainIDsRequest {}

message QueryProposedChainIDsResponse {
repeated ProposedChain proposedChains = 1
[ (gogoproto.nullable) = false ];
}

message ProposedChain {
string chainID = 1;
uint64 proposalID = 2;
}
28 changes: 28 additions & 0 deletions tests/e2e/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type State map[ChainID]ChainState
type ChainState struct {
ValBalances *map[ValidatorID]uint
Proposals *map[uint]Proposal
ProposedConsumerChains *[]string
ValPowers *map[ValidatorID]uint
RepresentativePowers *map[ValidatorID]uint
Params *[]Param
Expand Down Expand Up @@ -132,6 +133,11 @@ func (tr TestRun) getChainState(chain ChainID, modelState ChainState) ChainState
chainState.Proposals = &proposals
}

if modelState.ProposedConsumerChains != nil {
proposedConsumerChains := tr.getProposedConsumerChains(chain)
chainState.ProposedConsumerChains = &proposedConsumerChains
}

if modelState.ValPowers != nil {
tr.waitBlocks(chain, 1, 10*time.Second)
powers := tr.getValPowers(chain, *modelState.ValPowers)
Expand Down Expand Up @@ -772,3 +778,25 @@ func (tr TestRun) curlJsonRPCRequest(method, params, address string) {
verbosity := false
executeCommandWithVerbosity(cmd, "curlJsonRPCRequest", verbosity)
}

func (tr TestRun) getProposedConsumerChains(chain ChainID) []string {
tr.waitBlocks(chain, 1, 10*time.Second)
//#nosec G204 -- Bypass linter warning for spawning subprocess with cmd arguments.
bz, err := exec.Command("docker", "exec", tr.containerConfig.InstanceName, tr.chainConfigs[chain].BinaryName,
"query", "provider", "list-proposed-consumer-chains",
`--node`, tr.getQueryNode(chain),
`-o`, `json`,
).CombinedOutput()
if err != nil {
log.Fatal(err, "\n", string(bz))
}

arr := gjson.Get(string(bz), "proposedChains").Array()
chains := []string{}
for _, c := range arr {
cid := c.Get("chainID").String()
chains = append(chains, cid)
}

return chains
}
2 changes: 2 additions & 0 deletions tests/e2e/steps_start_chains.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ func stepsStartConsumerChain(consumerName string, proposalIndex, chainIndex uint
Status: "PROPOSAL_STATUS_VOTING_PERIOD",
},
},
ProposedConsumerChains: &[]string{consumerName},
},
},
},
Expand Down Expand Up @@ -163,6 +164,7 @@ func stepsStartConsumerChain(consumerName string, proposalIndex, chainIndex uint
ValidatorID("bob"): 9500000000,
ValidatorID("carol"): 9500000000,
},
ProposedConsumerChains: &[]string{},
},
ChainID(consumerName): ChainState{
ValBalances: &map[ValidatorID]uint{
Expand Down
28 changes: 28 additions & 0 deletions x/ccv/provider/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ func NewQueryCmd() *cobra.Command {
cmd.AddCommand(CmdThrottleState())
cmd.AddCommand(CmdThrottledConsumerPacketData())
cmd.AddCommand(CmdRegisteredConsumerRewardDenoms())
cmd.AddCommand(CmdProposedConsumerChains())

return cmd
}
Expand Down Expand Up @@ -93,6 +94,33 @@ func CmdConsumerChains() *cobra.Command {
return cmd
}

func CmdProposedConsumerChains() *cobra.Command {
cmd := &cobra.Command{
Use: "list-proposed-consumer-chains",
Short: "Query chainIDs in consumer addition proposal before voting finishes",
Args: cobra.ExactArgs(0),
RunE: func(cmd *cobra.Command, args []string) (err error) {
clientCtx, err := client.GetClientQueryContext(cmd)
if err != nil {
return err
}
queryClient := types.NewQueryClient(clientCtx)

req := &types.QueryProposedChainIDsRequest{}
res, err := queryClient.QueryProposedConsumerChainIDs(cmd.Context(), req)
if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)

return cmd
}

func CmdConsumerStartProposals() *cobra.Command {
cmd := &cobra.Command{
Use: "list-start-proposals",
Expand Down
101 changes: 101 additions & 0 deletions x/ccv/provider/keeper/gov_hook.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package keeper

import (
"fmt"

"github.com/cosmos/gogoproto/proto"

sdk "github.com/cosmos/cosmos-sdk/types"
govkeeper "github.com/cosmos/cosmos-sdk/x/gov/keeper"
sdkgov "github.com/cosmos/cosmos-sdk/x/gov/types"
v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1"

"github.com/cosmos/interchain-security/v3/x/ccv/provider/types"
)

type GovHooks struct {
gk *govkeeper.Keeper
k *Keeper
}

// Implements GovHooks interface
// GovHooks exist in cosmos-sdk/x/gov/keeper/hooks.go of v0.45.16-lsm-ics and on
var _ sdkgov.GovHooks = GovHooks{}

func (k *Keeper) GovHooks(gk *govkeeper.Keeper) GovHooks {
return GovHooks{
gk: gk,
k: k,
}
}

// AfterProposalSubmission - call hook if registered
// After consumerAddition proposal submission, the consumer chainID is stored
func (gh GovHooks) AfterProposalSubmission(ctx sdk.Context, proposalID uint64) {
p, ok := gh.gk.GetProposal(ctx, proposalID)
if !ok {
panic(fmt.Errorf("failed to get proposal %d in gov hook", proposalID))
}
msgs := p.GetMessages()

for _, msg := range msgs {
var msgLegacyContent v1.MsgExecLegacyContent
err := proto.Unmarshal(msg.Value, &msgLegacyContent)
if err != nil {
panic(fmt.Errorf("failed to unmarshal proposal content in gov hook: %w", err))
}

// if the proposal is not ConsumerAdditionProposal, continue
if msgLegacyContent.Content.TypeUrl != "/interchain_security.ccv.provider.v1.ConsumerAdditionProposal" {
yaruwangway marked this conversation as resolved.
Show resolved Hide resolved
continue
}
// if the consumer addition proposal cannot be unmarshaled, continue
var consAdditionProp types.ConsumerAdditionProposal
if err := proto.Unmarshal(msgLegacyContent.Content.Value, &consAdditionProp); err != nil {
continue
}

if consAdditionProp.ProposalType() == types.ProposalTypeConsumerAddition {
gh.k.SetProposedConsumerChain(ctx, consAdditionProp.ChainId, proposalID)
}
}
}

// AfterProposalVotingPeriodEnded - call hook if registered
// After proposal voting ends, the consumer chainID in store is deleted.
// When a proposal passes, this chainID will be available in providerKeeper.GetAllPendingConsumerAdditionProps
// or providerKeeper.GetAllConsumerChains(ctx).
func (gh GovHooks) AfterProposalVotingPeriodEnded(ctx sdk.Context, proposalID uint64) {
p, ok := gh.gk.GetProposal(ctx, proposalID)
if !ok {
panic(fmt.Errorf("failed to get proposal %d in gov hook", proposalID))
}
msgs := p.GetMessages()

for _, msg := range msgs {
var msgLegacyContent v1.MsgExecLegacyContent
err := proto.Unmarshal(msg.Value, &msgLegacyContent)
if err != nil {
panic(fmt.Errorf("failed to unmarshal proposal content in gov hook: %w", err))
}

if msgLegacyContent.Content.TypeUrl != "/interchain_security.ccv.provider.v1.ConsumerAdditionProposal" {
yaruwangway marked this conversation as resolved.
Show resolved Hide resolved
continue
}
var consAdditionProp types.ConsumerAdditionProposal
// if the proposal is not ConsumerAdditionProposal, return
if err := proto.Unmarshal(msgLegacyContent.Content.Value, &consAdditionProp); err != nil {
continue
}

if consAdditionProp.ProposalType() == types.ProposalTypeConsumerAddition {
gh.k.DeleteChainsInProposal(ctx, consAdditionProp.ChainId, proposalID)
}

}
}

func (gh GovHooks) AfterProposalDeposit(ctx sdk.Context, proposalID uint64, depositorAddr sdk.AccAddress) {
}
func (gh GovHooks) AfterProposalVote(ctx sdk.Context, proposalID uint64, voterAddr sdk.AccAddress) {}
func (gh GovHooks) AfterProposalFailedMinDeposit(ctx sdk.Context, proposalID uint64) {}
14 changes: 14 additions & 0 deletions x/ccv/provider/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,17 @@ func (k Keeper) QueryRegisteredConsumerRewardDenoms(goCtx context.Context, req *
Denoms: denoms,
}, nil
}

func (k Keeper) QueryProposedConsumerChainIDs(goCtx context.Context, req *types.QueryProposedChainIDsRequest) (*types.QueryProposedChainIDsResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "empty request")
}

ctx := sdk.UnwrapSDKContext(goCtx)

chains := k.GetAllProposedConsumerChainIDs(ctx)

return &types.QueryProposedChainIDsResponse{
ProposedChains: chains,
}, nil
}
44 changes: 44 additions & 0 deletions x/ccv/provider/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,50 @@ func (k Keeper) DeleteChainToChannel(ctx sdk.Context, chainID string) {
store.Delete(types.ChainToChannelKey(chainID))
}

// SetProposedConsumerChain stores a consumer chainId corresponding to a submitted consumer addition proposal
// This consumer chainId is deleted once the voting period for the proposal ends
// does not end.
func (k Keeper) SetProposedConsumerChain(ctx sdk.Context, chainID string, proposalID uint64) {
store := ctx.KVStore(k.storeKey)
store.Set(types.ProposedConsumerChainKey(proposalID), []byte(chainID))
}

// GetProposedConsumerChain get the proposed chainID in consumerAddition proposal.
func (k Keeper) GetProposedConsumerChain(ctx sdk.Context, proposalID uint64) string {
yaruwangway marked this conversation as resolved.
Show resolved Hide resolved
store := ctx.KVStore(k.storeKey)
return string(store.Get(types.ProposedConsumerChainKey(proposalID)))
}

// DeleteChainsInProposal deletes the consumer chainID from store
// which is in gov consumerAddition proposal
func (k Keeper) DeleteChainsInProposal(ctx sdk.Context, chainID string, proposalID uint64) {
yaruwangway marked this conversation as resolved.
Show resolved Hide resolved
store := ctx.KVStore(k.storeKey)
store.Delete(types.ProposedConsumerChainKey(proposalID))
}

// GetAllProposedConsumerChainIDs get consumer chainId in gov consumerAddition proposal before voting period ends.
func (k Keeper) GetAllProposedConsumerChainIDs(ctx sdk.Context) []types.ProposedChain {
yaruwangway marked this conversation as resolved.
Show resolved Hide resolved
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, []byte{types.ProposedConsumerChainByteKey})
defer iterator.Close()

proposedChains := []types.ProposedChain{}
for ; iterator.Valid(); iterator.Next() {
proposalID, err := types.ParseProposedConsumerChainKey(types.ProposedConsumerChainByteKey, iterator.Key())
if err != nil {
panic(fmt.Errorf("proposed chains cannot be parsed: %w", err))
}

proposedChains = append(proposedChains, types.ProposedChain{
ChainID: string(iterator.Value()),
ProposalID: proposalID,
})

}

return proposedChains
}

// GetAllConsumerChains gets all of the consumer chains, for which the provider module
// created IBC clients. Consumer chains with created clients are also referred to as registered.
//
Expand Down
20 changes: 20 additions & 0 deletions x/ccv/provider/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -533,3 +533,23 @@ func TestSetSlashLog(t *testing.T) {
require.True(t, providerKeeper.GetSlashLog(ctx, addrWithDoubleSigns))
require.False(t, providerKeeper.GetSlashLog(ctx, addrWithoutDoubleSigns))
}

func TestSetProposedConsumerChains(t *testing.T) {
providerKeeper, ctx, ctrl, _ := testkeeper.GetProviderKeeperAndCtx(t, testkeeper.NewInMemKeeperParams(t))
defer ctrl.Finish()

tests := []struct {
chainID string
proposalID uint64
}{
{chainID: "1", proposalID: 1},
{chainID: "some other ID", proposalID: 12},
{chainID: "some other other chain ID", proposalID: 123},
}

for _, test := range tests {
providerKeeper.SetProposedConsumerChain(ctx, test.chainID, test.proposalID)
cID := providerKeeper.GetProposedConsumerChain(ctx, test.proposalID)
require.Equal(t, cID, test.chainID)
}
}
22 changes: 22 additions & 0 deletions x/ccv/provider/types/keys.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,8 @@ const (
// handled in the current block
VSCMaturedHandledThisBlockBytePrefix

// ProposedConsumerChainByteKey is the byte prefix storing the consumer chainId in consumerAddition gov proposal submitted before voting finishes
ProposedConsumerChainByteKey
// NOTE: DO NOT ADD NEW BYTE PREFIXES HERE WITHOUT ADDING THEM TO getAllKeyPrefixes() IN keys_test.go
)

Expand Down Expand Up @@ -484,6 +486,26 @@ func VSCMaturedHandledThisBlockKey() []byte {
return []byte{VSCMaturedHandledThisBlockBytePrefix}
}

// ProposedConsumerChainKey returns the key of proposed consumer chainId in consumerAddition gov proposal before voting finishes, the stored key format is prefix|proposalID, value is chainID
func ProposedConsumerChainKey(proposalID uint64) []byte {
return ccvtypes.AppendMany(
[]byte{ProposedConsumerChainByteKey},
sdk.Uint64ToBigEndian(proposalID),
)
}

// ParseProposedConsumerChainKey get the proposalID in the key
func ParseProposedConsumerChainKey(prefix byte, bz []byte) (uint64, error) {
expectedPrefix := []byte{prefix}
prefixL := len(expectedPrefix)
if prefix := bz[:prefixL]; !bytes.Equal(prefix, expectedPrefix) {
return 0, fmt.Errorf("invalid prefix; expected: %X, got: %X", expectedPrefix, prefix)
}
proposalID := sdk.BigEndianToUint64(bz[prefixL:])

return proposalID, nil
}

//
// End of generic helpers section
//
Loading
Loading