refactor: x/upgrade audit changes (#14188)

This commit is contained in:
Likhita Polavarapu 2022-12-08 17:32:49 +05:30 committed by GitHub
parent f3be41836f
commit bd59987df1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 282 additions and 27 deletions

View File

@ -27,7 +27,7 @@ type MsgClient interface {
// Since: cosmos-sdk 0.46
SoftwareUpgrade(ctx context.Context, in *MsgSoftwareUpgrade, opts ...grpc.CallOption) (*MsgSoftwareUpgradeResponse, error)
// CancelUpgrade is a governance operation for cancelling a previously
// approvid software upgrade.
// approved software upgrade.
//
// Since: cosmos-sdk 0.46
CancelUpgrade(ctx context.Context, in *MsgCancelUpgrade, opts ...grpc.CallOption) (*MsgCancelUpgradeResponse, error)
@ -68,7 +68,7 @@ type MsgServer interface {
// Since: cosmos-sdk 0.46
SoftwareUpgrade(context.Context, *MsgSoftwareUpgrade) (*MsgSoftwareUpgradeResponse, error)
// CancelUpgrade is a governance operation for cancelling a previously
// approvid software upgrade.
// approved software upgrade.
//
// Since: cosmos-sdk 0.46
CancelUpgrade(context.Context, *MsgCancelUpgrade) (*MsgCancelUpgradeResponse, error)

View File

@ -2256,7 +2256,6 @@ type Plan struct {
// Deprecated: Do not use.
Time *timestamppb.Timestamp `protobuf:"bytes,2,opt,name=time,proto3" json:"time,omitempty"`
// The height at which the upgrade must be performed.
// Only used if Time is not set.
Height int64 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"`
// Any application specific upgrade info to be included on-chain
// such as a git commit that validators could automatically upgrade to
@ -2337,9 +2336,12 @@ type SoftwareUpgradeProposal struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
// title of the proposal
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
// description of the proposal
Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
Plan *Plan `protobuf:"bytes,3,opt,name=plan,proto3" json:"plan,omitempty"`
// plan of the proposal
Plan *Plan `protobuf:"bytes,3,opt,name=plan,proto3" json:"plan,omitempty"`
}
func (x *SoftwareUpgradeProposal) Reset() {
@ -2394,7 +2396,9 @@ type CancelSoftwareUpgradeProposal struct {
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
// title of the proposal
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
// description of the proposal
Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
}

View File

@ -18,8 +18,9 @@ service Msg {
//
// Since: cosmos-sdk 0.46
rpc SoftwareUpgrade(MsgSoftwareUpgrade) returns (MsgSoftwareUpgradeResponse);
// CancelUpgrade is a governance operation for cancelling a previously
// approvid software upgrade.
// approved software upgrade.
//
// Since: cosmos-sdk 0.46
rpc CancelUpgrade(MsgCancelUpgrade) returns (MsgCancelUpgradeResponse);

View File

@ -31,7 +31,6 @@ message Plan {
[deprecated = true, (gogoproto.stdtime) = true, (gogoproto.nullable) = false, (amino.dont_omitempty) = true];
// The height at which the upgrade must be performed.
// Only used if Time is not set.
int64 height = 3;
// Any application specific upgrade info to be included on-chain
@ -54,8 +53,13 @@ message SoftwareUpgradeProposal {
option (amino.name) = "cosmos-sdk/SoftwareUpgradeProposal";
option (gogoproto.equal) = true;
// title of the proposal
string title = 1;
// description of the proposal
string description = 2;
// plan of the proposal
Plan plan = 3 [(gogoproto.nullable) = false, (amino.dont_omitempty) = true];
}
@ -69,7 +73,10 @@ message CancelSoftwareUpgradeProposal {
option (amino.name) = "cosmos-sdk/CancelSoftwareUpgradeProposal";
option (gogoproto.equal) = true;
// title of the proposal
string title = 1;
// description of the proposal
string description = 2;
}

View File

@ -11,7 +11,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/upgrade/types"
)
// GetQueryCmd returns the parent command for all x/upgrade CLi query commands.
// GetQueryCmd returns the parent command for all x/upgrade CLI query commands.
func GetQueryCmd() *cobra.Command {
cmd := &cobra.Command{
Use: types.ModuleName,

View File

@ -0,0 +1,176 @@
package cli_test
import (
"context"
"fmt"
"io"
"testing"
"github.com/stretchr/testify/require"
rpcclientmock "github.com/tendermint/tendermint/rpc/client/mock"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
svrcmd "github.com/cosmos/cosmos-sdk/server/cmd"
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
testutilmod "github.com/cosmos/cosmos-sdk/types/module/testutil"
"github.com/cosmos/cosmos-sdk/x/upgrade"
upgradecli "github.com/cosmos/cosmos-sdk/x/upgrade/client/cli"
)
func TestGetCurrentPlanCmd(t *testing.T) {
encCfg := testutilmod.MakeTestEncodingConfig(upgrade.AppModuleBasic{})
kr := keyring.NewInMemory(encCfg.Codec)
baseCtx := client.Context{}.
WithKeyring(kr).
WithTxConfig(encCfg.TxConfig).
WithCodec(encCfg.Codec).
WithClient(clitestutil.MockTendermintRPC{Client: rpcclientmock.Client{}}).
WithAccountRetriever(client.MockAccountRetriever{}).
WithOutput(io.Discard).
WithChainID("test-chain")
testCases := []struct {
name string
args []string
expCmdOutput string
}{
{
name: "json output",
args: []string{fmt.Sprintf("--%s=json", flags.FlagOutput)},
expCmdOutput: `[--output=json]`,
},
{
name: "text output",
args: []string{fmt.Sprintf("--%s=text", flags.FlagOutput)},
expCmdOutput: `[--output=text]`,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := svrcmd.CreateExecuteContext(context.Background())
cmd := upgradecli.GetCurrentPlanCmd()
cmd.SetOut(io.Discard)
require.NotNil(t, cmd)
cmd.SetContext(ctx)
cmd.SetArgs(tc.args)
require.NoError(t, client.SetCmdClientContextHandler(baseCtx, cmd))
require.Contains(t, fmt.Sprint(cmd), "plan [] [] get upgrade plan (if one exists)")
require.Contains(t, fmt.Sprint(cmd), tc.expCmdOutput)
})
}
}
func TestGetAppliedPlanCmd(t *testing.T) {
encCfg := testutilmod.MakeTestEncodingConfig(upgrade.AppModuleBasic{})
kr := keyring.NewInMemory(encCfg.Codec)
baseCtx := client.Context{}.
WithKeyring(kr).
WithTxConfig(encCfg.TxConfig).
WithCodec(encCfg.Codec).
WithClient(clitestutil.MockTendermintRPC{Client: rpcclientmock.Client{}}).
WithAccountRetriever(client.MockAccountRetriever{}).
WithOutput(io.Discard).
WithChainID("test-chain")
testCases := []struct {
name string
args []string
expCmdOutput string
}{
{
name: "json output",
args: []string{"test-upgrade", fmt.Sprintf("--%s=json", flags.FlagOutput)},
expCmdOutput: `[test-upgrade --output=json]`,
},
{
name: "text output",
args: []string{"test-upgrade", fmt.Sprintf("--%s=text", flags.FlagOutput)},
expCmdOutput: `[test-upgrade --output=text]`,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
ctx := svrcmd.CreateExecuteContext(context.Background())
cmd := upgradecli.GetAppliedPlanCmd()
cmd.SetOut(io.Discard)
require.NotNil(t, cmd)
cmd.SetContext(ctx)
cmd.SetArgs(tc.args)
require.NoError(t, client.SetCmdClientContextHandler(baseCtx, cmd))
require.Contains(t, fmt.Sprint(cmd), "applied [upgrade-name] [] [] block header for height at which a completed upgrade was applied")
require.Contains(t, fmt.Sprint(cmd), tc.expCmdOutput)
})
}
}
func TestGetModuleVersionsCmd(t *testing.T) {
encCfg := testutilmod.MakeTestEncodingConfig(upgrade.AppModuleBasic{})
kr := keyring.NewInMemory(encCfg.Codec)
baseCtx := client.Context{}.
WithKeyring(kr).
WithTxConfig(encCfg.TxConfig).
WithCodec(encCfg.Codec).
WithClient(clitestutil.MockTendermintRPC{Client: rpcclientmock.Client{}}).
WithAccountRetriever(client.MockAccountRetriever{}).
WithOutput(io.Discard).
WithChainID("test-chain")
testCases := []struct {
msg string
args []string
expCmdOutput string
}{
{
msg: "test full query with json output",
args: []string{fmt.Sprintf("--%s=1", flags.FlagHeight), fmt.Sprintf("--%s=json", flags.FlagOutput)},
expCmdOutput: `--height=1 --output=json`,
},
{
msg: "test full query with text output",
args: []string{fmt.Sprintf("--%s=1", flags.FlagHeight), fmt.Sprintf("--%s=text", flags.FlagOutput)},
expCmdOutput: `--height=1 --output=text`,
},
{
msg: "test single module",
args: []string{"bank", fmt.Sprintf("--%s=1", flags.FlagHeight)},
expCmdOutput: `bank --height=1`,
},
{
msg: "test non-existent module",
args: []string{"abcdefg", fmt.Sprintf("--%s=1", flags.FlagHeight)},
expCmdOutput: `abcdefg --height=1`,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.msg, func(t *testing.T) {
ctx := svrcmd.CreateExecuteContext(context.Background())
cmd := upgradecli.GetModuleVersionsCmd()
cmd.SetOut(io.Discard)
require.NotNil(t, cmd)
cmd.SetContext(ctx)
cmd.SetArgs(tc.args)
require.NoError(t, client.SetCmdClientContextHandler(baseCtx, cmd))
require.Contains(t, fmt.Sprint(cmd), "module_versions [optional module_name] [] [] get the list of module versions")
require.Contains(t, fmt.Sprint(cmd), tc.expCmdOutput)
})
}
}

View File

@ -60,6 +60,7 @@ func (k *Keeper) SetVersionSetter(vs xp.ProtocolVersionSetter) {
k.versionSetter = vs
}
// GetVersionSetter gets the protocol version field of baseapp
func (k *Keeper) GetVersionSetter() xp.ProtocolVersionSetter {
return k.versionSetter
}
@ -156,7 +157,7 @@ func (k Keeper) GetModuleVersions(ctx sdk.Context) []*types.ModuleVersion {
return mv
}
// gets the version for a given module, and returns true if it exists, false otherwise
// getModuleVersion gets the version for a given module, and returns true if it exists, false otherwise
func (k Keeper) getModuleVersion(ctx sdk.Context, name string) (uint64, bool) {
store := ctx.KVStore(k.storeKey)
it := sdk.KVStorePrefixIterator(store, []byte{types.VersionMapByte})
@ -223,7 +224,7 @@ func (k Keeper) GetUpgradedClient(ctx sdk.Context, height int64) ([]byte, bool)
return bz, true
}
// SetUpgradedConsensusState set the expected upgraded consensus state for the next version of this chain
// SetUpgradedConsensusState sets the expected upgraded consensus state for the next version of this chain
// using the last height committed on this chain.
func (k Keeper) SetUpgradedConsensusState(ctx sdk.Context, planHeight int64, bz []byte) error {
store := ctx.KVStore(k.storeKey)
@ -231,7 +232,7 @@ func (k Keeper) SetUpgradedConsensusState(ctx sdk.Context, planHeight int64, bz
return nil
}
// GetUpgradedConsensusState set the expected upgraded consensus state for the next version of this chain
// GetUpgradedConsensusState gets the expected upgraded consensus state for the next version of this chain
func (k Keeper) GetUpgradedConsensusState(ctx sdk.Context, lastHeight int64) ([]byte, bool) {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.UpgradedConsStateKey(lastHeight))

View File

@ -10,6 +10,7 @@ import (
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/cosmos/cosmos-sdk/baseapp"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
"github.com/cosmos/cosmos-sdk/testutil"
simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims"
sdk "github.com/cosmos/cosmos-sdk/types"
@ -25,6 +26,7 @@ import (
type KeeperTestSuite struct {
suite.Suite
key *storetypes.KVStoreKey
baseApp *baseapp.BaseApp
upgradeKeeper keeper.Keeper
homeDir string
@ -37,6 +39,7 @@ type KeeperTestSuite struct {
func (s *KeeperTestSuite) SetupTest() {
s.encCfg = moduletestutil.MakeTestEncodingConfig(upgrade.AppModuleBasic{})
key := sdk.NewKVStoreKey(types.StoreKey)
s.key = key
testCtx := testutil.DefaultContextWithDB(s.T(), key, sdk.NewTransientStoreKey("transient_test"))
s.baseApp = baseapp.NewBaseApp(
@ -52,6 +55,10 @@ func (s *KeeperTestSuite) SetupTest() {
s.upgradeKeeper = keeper.NewKeeper(skipUpgradeHeights, key, s.encCfg.Codec, homeDir, nil, authtypes.NewModuleAddress(govtypes.ModuleName).String())
s.upgradeKeeper.SetVersionSetter(s.baseApp)
vs := s.upgradeKeeper.GetVersionSetter()
s.Require().Equal(vs, s.baseApp)
s.Require().Equal(testCtx.Ctx.Logger().With("module", "x/"+types.ModuleName), s.upgradeKeeper.Logger(testCtx.Ctx))
s.T().Log("home dir:", homeDir)
s.homeDir = homeDir
s.ctx = testCtx.Ctx.WithBlockHeader(tmproto.Header{Time: time.Now(), Height: 10})
@ -215,16 +222,49 @@ func (s *KeeperTestSuite) TestSetUpgradedClient() {
}
}
func (s *KeeperTestSuite) TestIsSkipHeight() {
var skipOne int64 = 9
ok := s.upgradeKeeper.IsSkipHeight(11)
s.Require().False(ok)
skip := map[int64]bool{skipOne: true}
upgradeKeeper := keeper.NewKeeper(skip, s.key, s.encCfg.Codec, s.T().TempDir(), nil, string(authtypes.NewModuleAddress(govtypes.ModuleName).String()))
upgradeKeeper.SetVersionSetter(s.baseApp)
s.Require().True(upgradeKeeper.IsSkipHeight(9))
s.Require().False(upgradeKeeper.IsSkipHeight(10))
}
func (s *KeeperTestSuite) TestUpgradedConsensusState() {
cs := []byte("IBC consensus state")
s.Require().NoError(s.upgradeKeeper.SetUpgradedConsensusState(s.ctx, 10, cs))
bz, ok := s.upgradeKeeper.GetUpgradedConsensusState(s.ctx, 10)
s.Require().True(ok)
s.Require().Equal(cs, bz)
}
func (s *KeeperTestSuite) TestDowngradeVerified() {
s.upgradeKeeper.SetDowngradeVerified(true)
ok := s.upgradeKeeper.DowngradeVerified()
s.Require().True(ok)
}
// Test that the protocol version successfully increments after an
// upgrade and is successfully set on BaseApp's appVersion.
func (s *KeeperTestSuite) TestIncrementProtocolVersion() {
oldProtocolVersion := s.baseApp.AppVersion()
s.upgradeKeeper.SetUpgradeHandler("dummy", func(_ sdk.Context, _ types.Plan, vm module.VersionMap) (module.VersionMap, error) { return vm, nil })
res := s.upgradeKeeper.HasHandler("dummy")
s.Require().False(res)
dummyPlan := types.Plan{
Name: "dummy",
Info: "some text here",
Height: 100,
}
s.Require().PanicsWithValue("ApplyUpgrade should never be called without first checking HasHandler",
func() {
s.upgradeKeeper.ApplyUpgrade(s.ctx, dummyPlan)
},
)
s.upgradeKeeper.SetUpgradeHandler("dummy", func(_ sdk.Context, _ types.Plan, vm module.VersionMap) (module.VersionMap, error) { return vm, nil })
s.upgradeKeeper.ApplyUpgrade(s.ctx, dummyPlan)
upgradedProtocolVersion := s.baseApp.AppVersion()

View File

@ -67,16 +67,17 @@ func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx client.Context, mux *g
}
}
// GetQueryCmd returns the cli query commands for this module
// GetQueryCmd returns the CLI query commands for this module
func (AppModuleBasic) GetQueryCmd() *cobra.Command {
return cli.GetQueryCmd()
}
// GetTxCmd returns the transaction commands for this module
// GetTxCmd returns the CLI transaction commands for this module
func (AppModuleBasic) GetTxCmd() *cobra.Command {
return cli.GetTxCmd()
}
// RegisterInterfaces registers interfaces and implementations of the upgrade module.
func (b AppModuleBasic) RegisterInterfaces(registry codectypes.InterfaceRegistry) {
types.RegisterInterfaces(registry)
}

View File

@ -22,6 +22,7 @@ func RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) {
legacy.RegisterAminoMsg(cdc, &MsgCancelUpgrade{}, "cosmos-sdk/MsgCancelUpgrade")
}
// RegisterInterfaces registers the interfaces types with the Interface Registry.
func RegisterInterfaces(registry types.InterfaceRegistry) {
registry.RegisterImplementations(
(*govtypes.Content)(nil),

View File

@ -16,7 +16,8 @@ const (
const (
// PlanByte specifies the Byte under which a pending upgrade plan is stored in the store
PlanByte = 0x0
// DoneByte is a prefix for to look up completed upgrade plan by name
// DoneByte is a prefix to look up completed upgrade plan by name
DoneByte = 0x1
// VersionMapByte is a prefix to look up module names (key) and versions (value)

View File

@ -9,6 +9,7 @@ const (
ProposalTypeCancelSoftwareUpgrade string = "CancelSoftwareUpgrade"
)
// NewSoftwareUpgradeProposal creates a new SoftwareUpgradeProposal instance
func NewSoftwareUpgradeProposal(title, description string, plan Plan) gov.Content {
return &SoftwareUpgradeProposal{title, description, plan}
}
@ -21,10 +22,19 @@ func init() {
gov.RegisterProposalType(ProposalTypeCancelSoftwareUpgrade)
}
func (sup *SoftwareUpgradeProposal) GetTitle() string { return sup.Title }
// GetTitle gets the proposal's title
func (sup *SoftwareUpgradeProposal) GetTitle() string { return sup.Title }
// GetDescription gets the proposal's description
func (sup *SoftwareUpgradeProposal) GetDescription() string { return sup.Description }
func (sup *SoftwareUpgradeProposal) ProposalRoute() string { return RouterKey }
func (sup *SoftwareUpgradeProposal) ProposalType() string { return ProposalTypeSoftwareUpgrade }
// ProposalRoute gets the proposal's router key
func (sup *SoftwareUpgradeProposal) ProposalRoute() string { return RouterKey }
// ProposalType is "SoftwareUpgrade"
func (sup *SoftwareUpgradeProposal) ProposalType() string { return ProposalTypeSoftwareUpgrade }
// ValidateBasic validates the proposal
func (sup *SoftwareUpgradeProposal) ValidateBasic() error {
if err := sup.Plan.ValidateBasic(); err != nil {
return err
@ -32,6 +42,7 @@ func (sup *SoftwareUpgradeProposal) ValidateBasic() error {
return gov.ValidateAbstract(sup)
}
// NewCancelSoftwareUpgradeProposal creates a new CancelSoftwareUpgradeProposal instance
func NewCancelSoftwareUpgradeProposal(title, description string) gov.Content {
return &CancelSoftwareUpgradeProposal{title, description}
}
@ -39,13 +50,21 @@ func NewCancelSoftwareUpgradeProposal(title, description string) gov.Content {
// Implements Proposal Interface
var _ gov.Content = &CancelSoftwareUpgradeProposal{}
func (csup *CancelSoftwareUpgradeProposal) GetTitle() string { return csup.Title }
// GetTitle gets the proposal's title
func (csup *CancelSoftwareUpgradeProposal) GetTitle() string { return csup.Title }
// GetDescription gets the proposal's description
func (csup *CancelSoftwareUpgradeProposal) GetDescription() string { return csup.Description }
func (csup *CancelSoftwareUpgradeProposal) ProposalRoute() string { return RouterKey }
// ProposalRoute gets the proposal's router key
func (csup *CancelSoftwareUpgradeProposal) ProposalRoute() string { return RouterKey }
// ProposalType is "CancelSoftwareUpgrade"
func (csup *CancelSoftwareUpgradeProposal) ProposalType() string {
return ProposalTypeCancelSoftwareUpgrade
}
// ValidateBasic validates the proposal
func (csup *CancelSoftwareUpgradeProposal) ValidateBasic() error {
return gov.ValidateAbstract(csup)
}

View File

@ -270,7 +270,7 @@ type MsgClient interface {
// Since: cosmos-sdk 0.46
SoftwareUpgrade(ctx context.Context, in *MsgSoftwareUpgrade, opts ...grpc.CallOption) (*MsgSoftwareUpgradeResponse, error)
// CancelUpgrade is a governance operation for cancelling a previously
// approvid software upgrade.
// approved software upgrade.
//
// Since: cosmos-sdk 0.46
CancelUpgrade(ctx context.Context, in *MsgCancelUpgrade, opts ...grpc.CallOption) (*MsgCancelUpgradeResponse, error)
@ -309,7 +309,7 @@ type MsgServer interface {
// Since: cosmos-sdk 0.46
SoftwareUpgrade(context.Context, *MsgSoftwareUpgrade) (*MsgSoftwareUpgradeResponse, error)
// CancelUpgrade is a governance operation for cancelling a previously
// approvid software upgrade.
// approved software upgrade.
//
// Since: cosmos-sdk 0.46
CancelUpgrade(context.Context, *MsgCancelUpgrade) (*MsgCancelUpgradeResponse, error)

View File

@ -45,7 +45,6 @@ type Plan struct {
// If this field is not empty, an error will be thrown.
Time time.Time `protobuf:"bytes,2,opt,name=time,proto3,stdtime" json:"time"` // Deprecated: Do not use.
// The height at which the upgrade must be performed.
// Only used if Time is not set.
Height int64 `protobuf:"varint,3,opt,name=height,proto3" json:"height,omitempty"`
// Any application specific upgrade info to be included on-chain
// such as a git commit that validators could automatically upgrade to
@ -96,9 +95,12 @@ var xxx_messageInfo_Plan proto.InternalMessageInfo
//
// Deprecated: Do not use.
type SoftwareUpgradeProposal struct {
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
// title of the proposal
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
// description of the proposal
Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
Plan Plan `protobuf:"bytes,3,opt,name=plan,proto3" json:"plan"`
// plan of the proposal
Plan Plan `protobuf:"bytes,3,opt,name=plan,proto3" json:"plan"`
}
func (m *SoftwareUpgradeProposal) Reset() { *m = SoftwareUpgradeProposal{} }
@ -141,7 +143,9 @@ var xxx_messageInfo_SoftwareUpgradeProposal proto.InternalMessageInfo
//
// Deprecated: Do not use.
type CancelSoftwareUpgradeProposal struct {
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
// title of the proposal
Title string `protobuf:"bytes,1,opt,name=title,proto3" json:"title,omitempty"`
// description of the proposal
Description string `protobuf:"bytes,2,opt,name=description,proto3" json:"description,omitempty"`
}