Skip to content

Commit 2f2beb6

Browse files
authored
Move minimum required pigeon version from hard-coded to governance (#1180)
# Related Github tickets - VolumeFi#1640 # Background At the moment, we store the minimum required version of Pigeon as hard-coded const inside Paloma. This means to enforce a Pigeon update, we need to roll-out a new version of the protocol as well, which often creates unnecessary noise and maintenance overhead. Instead, this information should live on the chain directly, and be addressable via governance vote. - Generalize the restriction to pigeon requirements, so we can add new restriction in the future, instead of only minimum version - Add pigeon requirements to valset keeper, as the valset module is the one requiring this information - Add scheduled pigeon requirements, so we can schedule rollouts to a specific block height - Add new gRPC query - `q valset get-pigeon-requirements` - to get current and scheduled requirements - Add new tx - `tx gov submit-legacy-proposal valset propose-pigeon-requirements` - to propose changes to pigeon requirements - The valset module checks for pending scheduled changes at each `BeginBlock`. When a change is applied, scheduled changes are cleared - Added this information to genesis # Testing completed - [x] test coverage exists or has been added/updated - [x] tested in a private testnet # Breaking changes - [x] I have checked my code for breaking changes - [x] If there are breaking changes, there is a supporting migration.
1 parent bad8273 commit 2f2beb6

34 files changed

+2350
-132
lines changed

app/app.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ import (
145145
treasurymodulekeeper "github.com/palomachain/paloma/x/treasury/keeper"
146146
treasurymoduletypes "github.com/palomachain/paloma/x/treasury/types"
147147
valsetmodule "github.com/palomachain/paloma/x/valset"
148+
valsetclient "github.com/palomachain/paloma/x/valset/client"
148149
valsetmodulekeeper "github.com/palomachain/paloma/x/valset/keeper"
149150
valsetmoduletypes "github.com/palomachain/paloma/x/valset/types"
150151
"github.com/spf13/cast"
@@ -154,8 +155,6 @@ const (
154155
Name = "paloma"
155156

156157
wasmAvailableCapabilities = "iterator,staking,stargate,paloma"
157-
158-
minimumPigeonVersion = "v1.11.2"
159158
)
160159

161160
func getGovProposalHandlers() []govclient.ProposalHandler {
@@ -165,6 +164,7 @@ func getGovProposalHandlers() []govclient.ProposalHandler {
165164
treasuryclient.ProposalHandler,
166165
evmclient.ProposalHandler,
167166
treasuryclient.ProposalHandler,
167+
valsetclient.ProposalHandler,
168168
}
169169
}
170170

@@ -558,7 +558,6 @@ func New(
558558
app.GetSubspace(valsetmoduletypes.ModuleName),
559559
app.StakingKeeper,
560560
app.SlashingKeeper,
561-
minimumPigeonVersion,
562561
sdk.DefaultPowerReduction,
563562
authcodec.NewBech32Codec(chainparams.ValidatorAddressPrefix),
564563
)
@@ -661,7 +660,8 @@ func New(
661660
AddRoute(evmmoduletypes.RouterKey, evm.NewReferenceChainReferenceIDProposalHandler(app.EvmKeeper)).
662661
AddRoute(ibcclienttypes.RouterKey, ibcclient.NewClientProposalHandler(app.IBCKeeper.ClientKeeper)).
663662
AddRoute(gravitymoduletypes.RouterKey, gravitymodulekeeper.NewGravityProposalHandler(app.GravityKeeper)).
664-
AddRoute(treasurymoduletypes.RouterKey, treasurymodule.NewFeeProposalHandler(app.TreasuryKeeper))
663+
AddRoute(treasurymoduletypes.RouterKey, treasurymodule.NewFeeProposalHandler(app.TreasuryKeeper)).
664+
AddRoute(valsetmoduletypes.RouterKey, valsetmodule.NewValsetProposalHandler(app.ValsetKeeper))
665665

666666
// Example of setting gov params:
667667
govKeeper := govkeeper.NewKeeper(

proto/palomachain/paloma/valset/genesis.proto

+6-1
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@ package palomachain.paloma.valset;
33

44
import "gogoproto/gogo.proto";
55
import "palomachain/paloma/valset/params.proto";
6+
import "palomachain/paloma/valset/pigeon_requirements.proto";
67

78
option go_package = "github.com/palomachain/paloma/x/valset/types";
89

910
// GenesisState defines the valset module's genesis state.
10-
message GenesisState { Params params = 1 [ (gogoproto.nullable) = false ]; }
11+
message GenesisState {
12+
Params params = 1 [ (gogoproto.nullable) = false ];
13+
PigeonRequirements pigeonRequirements = 2;
14+
ScheduledPigeonRequirements scheduledPigeonRequirements = 3;
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
syntax = "proto3";
2+
package palomachain.paloma.valset;
3+
4+
option go_package = "github.com/palomachain/paloma/x/valset/types";
5+
6+
message PigeonRequirements {
7+
string minVersion = 1;
8+
}
9+
10+
message ScheduledPigeonRequirements {
11+
PigeonRequirements requirements = 1;
12+
uint64 targetBlockHeight = 2;
13+
}

proto/palomachain/paloma/valset/query.proto

+15
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import "google/api/annotations.proto";
66
import "cosmos/base/query/v1beta1/pagination.proto";
77
import "palomachain/paloma/valset/params.proto";
88
import "palomachain/paloma/valset/snapshot.proto";
9+
import "palomachain/paloma/valset/pigeon_requirements.proto";
910
import "google/protobuf/timestamp.proto";
1011

1112
option go_package = "github.com/palomachain/paloma/x/valset/types";
@@ -54,6 +55,13 @@ service Query {
5455
option (google.api.http).get =
5556
"/palomachain/paloma/valset/get_alive_pigeons";
5657
}
58+
59+
// Queries the PigeonRequirements
60+
rpc GetPigeonRequirements(QueryGetPigeonRequirementsRequest)
61+
returns (QueryGetPigeonRequirementsResponse) {
62+
option (google.api.http).get =
63+
"/palomachain/paloma/valset/get_pigeon_requirements";
64+
}
5765
}
5866

5967
// QueryParamsRequest is request type for the Query/Params RPC method.
@@ -112,3 +120,10 @@ message QueryGetAlivePigeonsResponse {
112120

113121
repeated ValidatorAlive aliveValidators = 1;
114122
}
123+
124+
message QueryGetPigeonRequirementsRequest {}
125+
126+
message QueryGetPigeonRequirementsResponse {
127+
PigeonRequirements pigeonRequirements = 1;
128+
ScheduledPigeonRequirements scheduledPigeonRequirements = 2;
129+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
syntax = "proto3";
2+
package palomachain.paloma.valset;
3+
4+
option go_package = "github.com/palomachain/paloma/x/valset/types";
5+
6+
message SetPigeonRequirementsProposal {
7+
string title = 1;
8+
string description = 2;
9+
string minVersion = 3;
10+
uint64 targetBlockHeight = 4;
11+
}

tests/integration/evm/keeper/test_helpers_test.go

-5
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,6 @@ import (
7777
"github.com/spf13/cast"
7878
)
7979

80-
const (
81-
minimumPigeonVersion = "v1.10.0"
82-
)
83-
8480
type fixture struct {
8581
ctx sdk.Context
8682
codec codec.Codec
@@ -207,7 +203,6 @@ func initFixture(t ginkgo.FullGinkgoTInterface) *fixture {
207203
helper.GetSubspace(valsetmoduletypes.ModuleName, paramsKeeper),
208204
stakingKeeper,
209205
slashingKeeper,
210-
minimumPigeonVersion,
211206
sdk.DefaultPowerReduction,
212207
authcodec.NewBech32Codec(params2.ValidatorAddressPrefix),
213208
)

tests/integration/paloma/keeper/test_helpers_test.go

-5
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,6 @@ import (
6767
"github.com/spf13/cast"
6868
)
6969

70-
const (
71-
minimumPigeonVersion = "v1.10.0"
72-
)
73-
7470
type fixture struct {
7571
ctx sdk.Context
7672
codec codec.Codec
@@ -175,7 +171,6 @@ func initFixture(t ginkgo.FullGinkgoTInterface) *fixture {
175171
helper.GetSubspace(valsetmoduletypes.ModuleName, paramsKeeper),
176172
stakingKeeper,
177173
slashingKeeper,
178-
minimumPigeonVersion,
179174
sdk.DefaultPowerReduction,
180175
authcodec.NewBech32Codec(params2.ValidatorAddressPrefix),
181176
)

tests/integration/scheduler/keeper/keeper_test.go

-5
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@ import (
5050
"google.golang.org/protobuf/reflect/protoreflect"
5151
)
5252

53-
const (
54-
minimumPigeonVersion = "v1.10.0"
55-
)
56-
5753
type fixture struct {
5854
ctx sdk.Context
5955

@@ -152,7 +148,6 @@ func initFixture(t ginkgo.FullGinkgoTInterface) *fixture {
152148
helper.GetSubspace(valsetmoduletypes.ModuleName, paramsKeeper),
153149
stakingKeeper,
154150
slashingKeeper,
155-
minimumPigeonVersion,
156151
sdk.DefaultPowerReduction,
157152
authcodec.NewBech32Codec(params2.ValidatorAddressPrefix),
158153
)

tests/integration/valset/keeper/keeper_test.go

-5
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,6 @@ import (
4848
"github.com/stretchr/testify/require"
4949
)
5050

51-
const (
52-
minimumPigeonVersion = "v1.10.0"
53-
)
54-
5551
type fixture struct {
5652
ctx sdk.Context
5753
storeKey cosmosstore.KVStoreService
@@ -141,7 +137,6 @@ func initFixture(t ginkgo.FullGinkgoTInterface) *fixture {
141137
helper.GetSubspace(valsetmoduletypes.ModuleName, paramsKeeper),
142138
stakingKeeper,
143139
slashingKeeper,
144-
minimumPigeonVersion,
145140
sdk.DefaultPowerReduction,
146141
authcodec.NewBech32Codec(params2.ValidatorAddressPrefix),
147142
)

testutil/keeper/valset.go

-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,6 @@ func ValsetKeeper(t testing.TB) (*keeper.Keeper, sdk.Context) {
4747
paramsSubspace,
4848
nil,
4949
nil,
50-
"v1.4.0",
5150
sdk.DefaultPowerReduction,
5251
address.NewBech32Codec("paloma"),
5352
)

x/gravity/keeper/test_common.go

-1
Original file line numberDiff line numberDiff line change
@@ -692,7 +692,6 @@ func CreateTestEnv(t *testing.T) TestInput {
692692
getSubspace(paramsKeeper, valsettypes.ModuleName),
693693
stakingKeeper,
694694
slashingKeeper,
695-
"v1.5.0",
696695
sdk.DefaultPowerReduction,
697696
authcodec.NewBech32Codec(chainparams.ValidatorAddressPrefix),
698697
)

x/valset/client/cli/query.go

+2
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,7 @@ func GetQueryCmd(queryRoute string) *cobra.Command {
3131

3232
cmd.AddCommand(CmdGetAlivePigeons())
3333

34+
cmd.AddCommand(CmdGetPigeonRequirements())
35+
3436
return cmd
3537
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package cli
2+
3+
import (
4+
"strconv"
5+
6+
"github.com/cosmos/cosmos-sdk/client"
7+
"github.com/cosmos/cosmos-sdk/client/flags"
8+
"github.com/palomachain/paloma/x/valset/types"
9+
"github.com/spf13/cobra"
10+
)
11+
12+
var _ = strconv.Itoa(0)
13+
14+
func CmdGetPigeonRequirements() *cobra.Command {
15+
cmd := &cobra.Command{
16+
Use: "get-pigeon-requirements",
17+
Short: "Query GetPigeonRequirements",
18+
Args: cobra.ExactArgs(0),
19+
RunE: func(cmd *cobra.Command, args []string) (err error) {
20+
clientCtx, err := client.GetClientTxContext(cmd)
21+
if err != nil {
22+
return err
23+
}
24+
25+
queryClient := types.NewQueryClient(clientCtx)
26+
27+
params := &types.QueryGetPigeonRequirementsRequest{}
28+
29+
res, err := queryClient.GetPigeonRequirements(cmd.Context(), params)
30+
if err != nil {
31+
return err
32+
}
33+
34+
return clientCtx.PrintProto(res)
35+
},
36+
}
37+
38+
flags.AddQueryFlagsToCmd(cmd)
39+
40+
return cmd
41+
}

x/valset/client/cli/tx_proposal.go

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package cli
2+
3+
import (
4+
"errors"
5+
6+
"github.com/cosmos/cosmos-sdk/client"
7+
"github.com/cosmos/cosmos-sdk/client/flags"
8+
"github.com/cosmos/cosmos-sdk/client/tx"
9+
sdk "github.com/cosmos/cosmos-sdk/types"
10+
"github.com/cosmos/cosmos-sdk/x/gov/client/cli"
11+
govv1beta1types "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1"
12+
"github.com/palomachain/paloma/x/valset/types"
13+
"github.com/spf13/cobra"
14+
"golang.org/x/mod/semver"
15+
)
16+
17+
const flagTargetBlockHeight = "target-block-height"
18+
19+
func applyFlags(cmd *cobra.Command) {
20+
flags.AddTxFlagsToCmd(cmd)
21+
22+
cmd.Flags().String(cli.FlagTitle, "", "title of proposal")
23+
cmd.Flags().String(cli.FlagSummary, "", "description of proposal")
24+
cmd.Flags().String(cli.FlagDeposit, "", "deposit of proposal")
25+
26+
//nolint:errcheck
27+
cmd.MarkFlagRequired(cli.FlagTitle)
28+
//nolint:errcheck
29+
cmd.MarkFlagRequired(cli.FlagSummary)
30+
}
31+
32+
func getCurrentVersion(cmd *cobra.Command, clientCtx client.Context) (string, error) {
33+
queryClient := types.NewQueryClient(clientCtx)
34+
queryParams := &types.QueryGetPigeonRequirementsRequest{}
35+
res, err := queryClient.GetPigeonRequirements(cmd.Context(), queryParams)
36+
if err != nil {
37+
return "", err
38+
}
39+
40+
return res.PigeonRequirements.MinVersion, nil
41+
}
42+
43+
func CmdValsetChainProposalHandler() *cobra.Command {
44+
cmd := &cobra.Command{
45+
Use: "valset",
46+
Short: "Valset proposals",
47+
}
48+
49+
cmd.AddCommand(CmdValsetProposePigeonRequirements())
50+
51+
return cmd
52+
}
53+
54+
func CmdValsetProposePigeonRequirements() *cobra.Command {
55+
cmd := &cobra.Command{
56+
Use: "propose-pigeon-requirements [minimum-version]",
57+
Short: "Proposal to set new pigeon requirements",
58+
Args: cobra.ExactArgs(1),
59+
RunE: func(cmd *cobra.Command, args []string) error {
60+
clientCtx, err := client.GetClientTxContext(cmd)
61+
if err != nil {
62+
return err
63+
}
64+
65+
title, err := cmd.Flags().GetString(cli.FlagTitle)
66+
if err != nil {
67+
return err
68+
}
69+
70+
description, err := cmd.Flags().GetString(cli.FlagTitle)
71+
if err != nil {
72+
return err
73+
}
74+
75+
blockHeight, err := cmd.Flags().GetUint64(flagTargetBlockHeight)
76+
if err != nil {
77+
return err
78+
}
79+
80+
curVersion, err := getCurrentVersion(cmd, clientCtx)
81+
if err != nil {
82+
return err
83+
}
84+
85+
minVersion := args[0]
86+
87+
if semver.Compare(minVersion, curVersion) < 0 {
88+
return errors.New("new version cannot be lower than current")
89+
}
90+
91+
prop := &types.SetPigeonRequirementsProposal{
92+
MinVersion: args[0],
93+
TargetBlockHeight: blockHeight,
94+
Title: title,
95+
Description: description,
96+
}
97+
98+
from := clientCtx.GetFromAddress()
99+
100+
depositStr, err := cmd.Flags().GetString(cli.FlagDeposit)
101+
if err != nil {
102+
return err
103+
}
104+
105+
deposit, err := sdk.ParseCoinsNormalized(depositStr)
106+
if err != nil {
107+
return err
108+
}
109+
110+
msg, err := govv1beta1types.NewMsgSubmitProposal(prop, deposit, from)
111+
if err != nil {
112+
return err
113+
}
114+
115+
err = tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg)
116+
if err != nil {
117+
return err
118+
}
119+
120+
return nil
121+
},
122+
}
123+
124+
cmd.Flags().Uint64(flagTargetBlockHeight, 0, "block height when requirements will be applied")
125+
126+
applyFlags(cmd)
127+
128+
return cmd
129+
}

x/valset/client/proposal_handler.go

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package client
2+
3+
import (
4+
govclient "github.com/cosmos/cosmos-sdk/x/gov/client"
5+
"github.com/palomachain/paloma/x/valset/client/cli"
6+
)
7+
8+
var ProposalHandler = govclient.NewProposalHandler(cli.CmdValsetChainProposalHandler)

0 commit comments

Comments
 (0)