Merge PR #4471: Migrate genesis cmd

This commit is contained in:
Karoly Albert Szabo 2019-07-03 18:21:34 +02:00 committed by Alexander Bezobchuk
parent 4e86810a27
commit 00f753d684
12 changed files with 1353 additions and 2 deletions

View File

@ -0,0 +1,3 @@
#4409 Implement a command that migrates exported state from one version to the next.
The `migrate` command currently supports migrating from v0.34 to v0.36 by implementing
necessary types for both versions.

View File

@ -5,8 +5,9 @@ import lib
def process_raw_genesis(genesis, parsed_args):
# update genesis with breaking changes
genesis['consensus_params']['block'] = genesis['consensus_params']['block_size']
del genesis['consensus_params']['block_size']
if 'block_size' in genesis['consensus_params']:
genesis['consensus_params']['block'] = genesis['consensus_params']['block_size']
del genesis['consensus_params']['block_size']
genesis['app_state']['crisis'] = {
'constant_fee': {

View File

@ -16,3 +16,11 @@ Light-clients enable users to interact with your application without having to d
- [Command-Line interface for SDK-based blockchain](./cli.md)
- [Service provider doc](./service-providers.md)
## Genesis upgrade
If you need to upgrade your node you could export the genesis and migrate it to the new version through this script:
```bash
<appbinary> migrate v0.36 genesis_0_34.json [--time "2019-04-22T17:00:11Z"] [--chain-id test] > ~/.gaiad/genesis.json
```

View File

@ -0,0 +1,87 @@
package cli
import (
"fmt"
"time"
"github.com/spf13/cobra"
"github.com/tendermint/tendermint/types"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/version"
extypes "github.com/cosmos/cosmos-sdk/x/genutil"
"github.com/cosmos/cosmos-sdk/x/genutil/legacy/v036"
)
var migrationMap = extypes.MigrationMap{
"v0.36": v036.Migrate,
}
const (
flagGenesisTime = "genesis-time"
flagChainId = "chain-id"
)
func MigrateGenesisCmd(_ *server.Context, cdc *codec.Codec) *cobra.Command {
cmd := &cobra.Command{
Use: "migrate [target-version] [genesis-file]",
Short: "Migrate genesis to a specified target version",
Long: fmt.Sprintf(`Migrate the source genesis into the target version and print to STDOUT.
Example:
$ %s migrate v0.36 /path/to/genesis.json --chain-id=cosmoshub-3 --genesis-time=2019-04-22T17:00:00Z
`, version.ServerName),
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
target := args[0]
importGenesis := args[1]
genDoc, err := types.GenesisDocFromFile(importGenesis)
if err != nil {
return err
}
var initialState extypes.AppMap
cdc.MustUnmarshalJSON(genDoc.AppState, &initialState)
if migrationMap[target] == nil {
return fmt.Errorf("unknown migration function version: %s", target)
}
newGenState := migrationMap[target](initialState, cdc)
genDoc.AppState = cdc.MustMarshalJSON(newGenState)
genesisTime := cmd.Flag(flagGenesisTime).Value.String()
if genesisTime != "" {
var t time.Time
err := t.UnmarshalText([]byte(genesisTime))
if err != nil {
return err
}
genDoc.GenesisTime = t
}
chainId := cmd.Flag(flagChainId).Value.String()
if chainId != "" {
genDoc.ChainID = chainId
}
out, err := cdc.MarshalJSONIndent(genDoc, "", " ")
if err != nil {
return err
}
fmt.Println(string(out))
return nil
},
}
cmd.Flags().String(flagGenesisTime, "", "Override genesis_time with this flag")
cmd.Flags().String(flagChainId, "", "Override chain_id with this flag")
return cmd
}

View File

@ -0,0 +1,58 @@
package cli
import (
"io/ioutil"
"path"
"testing"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"github.com/stretchr/testify/require"
tcmd "github.com/tendermint/tendermint/cmd/tendermint/commands"
"github.com/tendermint/tendermint/libs/cli"
"github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/server"
"github.com/cosmos/cosmos-sdk/tests"
)
func setupCmd(genesisTime string, chainId string) *cobra.Command {
c := &cobra.Command{
Use: "c",
Args: cobra.ArbitraryArgs,
Run: func(_ *cobra.Command, args []string) {},
}
c.Flags().String(flagGenesisTime, genesisTime, "")
c.Flags().String(flagChainId, chainId, "")
return c
}
func TestMigrateGenesis(t *testing.T) {
home, cleanup := tests.NewTestCaseDir(t)
viper.Set(cli.HomeFlag, home)
viper.Set(client.FlagName, "moniker")
logger := log.NewNopLogger()
cfg, err := tcmd.ParseConfig()
require.Nil(t, err)
ctx := server.NewContext(cfg, logger)
cdc := makeCodec()
genesisPath := path.Join(home, "genesis.json")
target := "v0.36"
defer cleanup()
// Reject if we dont' have the right parameters or genesis does not exists
require.Error(t, MigrateGenesisCmd(ctx, cdc).RunE(nil, []string{target, genesisPath}))
// Noop migration with minimal genesis
emptyGenesis := []byte(`{"chain_id":"test","app_state":{}}`)
err = ioutil.WriteFile(genesisPath, emptyGenesis, 0644)
require.Nil(t, err)
cmd := setupCmd("", "test2")
require.NoError(t, MigrateGenesisCmd(ctx, cdc).RunE(cmd, []string{target, genesisPath}))
// Every migration function shuold tests its own module separately
}

View File

@ -0,0 +1,26 @@
package v036
import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/x/genutil"
v034gov "github.com/cosmos/cosmos-sdk/x/gov/legacy/v034"
v036gov "github.com/cosmos/cosmos-sdk/x/gov/legacy/v036"
)
// Migrate migrates exported state from v0.34 to a v0.36 genesis state.
func Migrate(appState genutil.AppMap, cdc *codec.Codec) genutil.AppMap {
v034Codec := codec.New()
codec.RegisterCrypto(v034Codec)
v036Codec := codec.New()
codec.RegisterCrypto(v036Codec)
if appState[v034gov.ModuleName] != nil {
var govState v034gov.GenesisState
v034gov.RegisterCodec(v034Codec)
v034Codec.MustUnmarshalJSON(appState[v034gov.ModuleName], &govState)
v036gov.RegisterCodec(v036Codec)
delete(appState, v034gov.ModuleName) // Drop old key, in case it changed name
appState[v036gov.ModuleName] = v036Codec.MustMarshalJSON(v036gov.MigrateGovernance(govState))
}
return appState
}

View File

@ -0,0 +1,109 @@
package v036
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/tendermint/go-amino"
"github.com/cosmos/cosmos-sdk/x/genutil"
)
var basic034Gov = []byte(`
{
"starting_proposal_id": "2",
"deposits": [
{
"proposal_id": "1",
"deposit": {
"depositor": "cosmos1grgelyng2v6v3t8z87wu3sxgt9m5s03xvslewd",
"proposal_id": "1",
"amount": [
{
"denom": "uatom",
"amount": "512000000"
}
]
}
}
],
"votes" : [
{
"proposal_id": "1",
"vote": {
"voter": "cosmos1lktjhnzkpkz3ehrg8psvmwhafg56kfss5597tg",
"proposal_id": "1",
"option": "Yes"
}
}
],
"proposals": [
{
"proposal_content": {
"type": "gov/TextProposal",
"value": {
"title": "test",
"description": "test"
}
},
"proposal_id": "1",
"proposal_status": "Passed",
"final_tally_result": {
"yes": "1",
"abstain": "0",
"no": "0",
"no_with_veto": "0"
},
"submit_time": "2019-05-03T21:08:25.443199036Z",
"deposit_end_time": "2019-05-17T21:08:25.443199036Z",
"total_deposit": [
{
"denom": "uatom",
"amount": "512000000"
}
],
"voting_start_time": "2019-05-04T16:02:33.24680295Z",
"voting_end_time": "2019-05-18T16:02:33.24680295Z"
}
],
"deposit_params": {
"min_deposit": [
{
"denom": "uatom",
"amount": "512000000"
}
],
"max_deposit_period": "1209600000000000"
},
"voting_params": {
"voting_period": "1209600000000000"
},
"tally_params": {
"quorum": "0.400000000000000000",
"threshold": "0.500000000000000000",
"veto": "0.334000000000000000"
}
}
`)
func TestDummyGenesis(t *testing.T) {
genesisDummy := genutil.AppMap{
"foo": {},
"bar": []byte(`{"custom": "module"}`),
}
cdc := amino.NewCodec()
migratedDummy := Migrate(genesisDummy, cdc)
// We should not touch custom modules in the map
require.Equal(t, genesisDummy["foo"], migratedDummy["foo"])
require.Equal(t, genesisDummy["bar"], migratedDummy["bar"])
}
func TestGovGenesis(t *testing.T) {
genesis := genutil.AppMap{
"gov": basic034Gov,
}
cdc := amino.NewCodec()
require.NotPanics(t, func() { Migrate(genesis, cdc) })
}

16
x/genutil/types.go Normal file
View File

@ -0,0 +1,16 @@
package genutil
import (
"encoding/json"
"github.com/cosmos/cosmos-sdk/codec"
)
type (
// AppMap map modules names with their json raw representation
AppMap map[string]json.RawMessage
// MigrationCallback converts a genesis map from the previous version to the targeted one
MigrationCallback func(AppMap, *codec.Codec) AppMap
// MigrationMap defines a mapping from a version to a MigrationCallback
MigrationMap map[string]MigrationCallback
)

513
x/gov/legacy/v034/types.go Normal file
View File

@ -0,0 +1,513 @@
package v034
import (
"encoding/json"
"fmt"
"strings"
"time"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Keys
const (
// ModuleName is the name of the module
ModuleName = "gov"
// StoreKey is the store key string for gov
StoreKey = ModuleName
// RouterKey is the message route for gov
RouterKey = ModuleName
// QuerierRoute is the querier route for gov
QuerierRoute = ModuleName
// DefaultParamspace default name for parameter store
DefaultParamspace = ModuleName
)
type GenesisState struct {
StartingProposalID uint64 `json:"starting_proposal_id"`
Deposits []DepositWithMetadata `json:"deposits"`
Votes []VoteWithMetadata `json:"votes"`
Proposals []Proposal `json:"proposals"`
DepositParams DepositParams `json:"deposit_params"`
VotingParams VotingParams `json:"voting_params"`
TallyParams TallyParams `json:"tally_params"`
}
type DepositWithMetadata struct {
ProposalID uint64 `json:"proposal_id"`
Deposit Deposit `json:"deposit"`
}
type VoteWithMetadata struct {
ProposalID uint64 `json:"proposal_id"`
Vote Vote `json:"vote"`
}
type Deposit struct {
ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal
Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor
Amount sdk.Coins `json:"amount"` // Deposit amount
}
type Deposits []Deposit
type Vote struct {
ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal
Voter sdk.AccAddress `json:"voter"` // address of the voter
Option VoteOption `json:"option"` // option from OptionSet chosen by the voter
}
type Votes []Vote
// Param around deposits for governance
type DepositParams struct {
MinDeposit sdk.Coins `json:"min_deposit,omitempty"` // Minimum deposit for a proposal to enter voting period.
MaxDepositPeriod time.Duration `json:"max_deposit_period,omitempty"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
}
type TallyParams struct {
Quorum sdk.Dec `json:"quorum,omitempty"` // Minimum percentage of total stake needed to vote for a result to be considered valid
Threshold sdk.Dec `json:"threshold,omitempty"` // Minimum proportion of Yes votes for proposal to pass. Initial value: 0.5
Veto sdk.Dec `json:"veto,omitempty"` // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
}
type VotingParams struct {
VotingPeriod time.Duration `json:"voting_period,omitempty"` // Length of the voting period.
}
type TallyResult struct {
Yes sdk.Int `json:"yes"`
Abstain sdk.Int `json:"abstain"`
No sdk.Int `json:"no"`
NoWithVeto sdk.Int `json:"no_with_veto"`
}
// ----------------------------------------------------------------------------
// ProposalStatus
// ----------------------------------------------------------------------------
// ProposalStatus is a type alias that represents a proposal status as a byte
type ProposalStatus byte
//nolint
const (
StatusNil ProposalStatus = 0x00
StatusDepositPeriod ProposalStatus = 0x01
StatusVotingPeriod ProposalStatus = 0x02
StatusPassed ProposalStatus = 0x03
StatusRejected ProposalStatus = 0x04
StatusFailed ProposalStatus = 0x05
)
// ProposalStatusToString turns a string into a ProposalStatus
func ProposalStatusFromString(str string) (ProposalStatus, error) {
switch str {
case "DepositPeriod":
return StatusDepositPeriod, nil
case "VotingPeriod":
return StatusVotingPeriod, nil
case "Passed":
return StatusPassed, nil
case "Rejected":
return StatusRejected, nil
case "Failed":
return StatusFailed, nil
case "":
return StatusNil, nil
default:
return ProposalStatus(0xff), fmt.Errorf("'%s' is not a valid proposal status", str)
}
}
// Marshal needed for protobuf compatibility
func (status ProposalStatus) Marshal() ([]byte, error) {
return []byte{byte(status)}, nil
}
// Unmarshal needed for protobuf compatibility
func (status *ProposalStatus) Unmarshal(data []byte) error {
*status = ProposalStatus(data[0])
return nil
}
// Marshals to JSON using string
func (status ProposalStatus) MarshalJSON() ([]byte, error) {
return json.Marshal(status.String())
}
// Unmarshals from JSON assuming Bech32 encoding
func (status *ProposalStatus) UnmarshalJSON(data []byte) error {
var s string
err := json.Unmarshal(data, &s)
if err != nil {
return err
}
bz2, err := ProposalStatusFromString(s)
if err != nil {
return err
}
*status = bz2
return nil
}
// String implements the Stringer interface.
func (status ProposalStatus) String() string {
switch status {
case StatusDepositPeriod:
return "DepositPeriod"
case StatusVotingPeriod:
return "VotingPeriod"
case StatusPassed:
return "Passed"
case StatusRejected:
return "Rejected"
case StatusFailed:
return "Failed"
default:
return ""
}
}
// Format implements the fmt.Formatter interface.
// nolint: errcheck
func (status ProposalStatus) Format(s fmt.State, verb rune) {
switch verb {
case 's':
s.Write([]byte(status.String()))
default:
// TODO: Do this conversion more directly
s.Write([]byte(fmt.Sprintf("%v", byte(status))))
}
}
// ----------------------------------------------------------------------------
// VoteOption
// ----------------------------------------------------------------------------
type VoteOption byte
// Vote options
const (
OptionEmpty VoteOption = 0x00
OptionYes VoteOption = 0x01
OptionAbstain VoteOption = 0x02
OptionNo VoteOption = 0x03
OptionNoWithVeto VoteOption = 0x04
)
// VoteOptionFromString returns a VoteOption from a string. It returns an error
// if the string is invalid.
func VoteOptionFromString(str string) (VoteOption, error) {
switch str {
case "Yes":
return OptionYes, nil
case "Abstain":
return OptionAbstain, nil
case "No":
return OptionNo, nil
case "NoWithVeto":
return OptionNoWithVeto, nil
default:
return VoteOption(0xff), fmt.Errorf("'%s' is not a valid vote option", str)
}
}
// Marshal needed for protobuf compatibility.
func (vo VoteOption) Marshal() ([]byte, error) {
return []byte{byte(vo)}, nil
}
// Unmarshal needed for protobuf compatibility.
func (vo *VoteOption) Unmarshal(data []byte) error {
*vo = VoteOption(data[0])
return nil
}
// Marshals to JSON using string.
func (vo VoteOption) MarshalJSON() ([]byte, error) {
return json.Marshal(vo.String())
}
// UnmarshalJSON decodes from JSON assuming Bech32 encoding.
func (vo *VoteOption) UnmarshalJSON(data []byte) error {
var s string
err := json.Unmarshal(data, &s)
if err != nil {
return err
}
bz2, err := VoteOptionFromString(s)
if err != nil {
return err
}
*vo = bz2
return nil
}
// String implements the Stringer interface.
func (vo VoteOption) String() string {
switch vo {
case OptionYes:
return "Yes"
case OptionAbstain:
return "Abstain"
case OptionNo:
return "No"
case OptionNoWithVeto:
return "NoWithVeto"
default:
return ""
}
}
// Format implements the fmt.Formatter interface.
// nolint: errcheck
func (vo VoteOption) Format(s fmt.State, verb rune) {
switch verb {
case 's':
s.Write([]byte(vo.String()))
default:
s.Write([]byte(fmt.Sprintf("%v", byte(vo))))
}
}
// ----------------------------------------------------------------------------
// Tally
// ----------------------------------------------------------------------------
// Equals returns if two proposals are equal.
func (tr TallyResult) Equals(comp TallyResult) bool {
return tr.Yes.Equal(comp.Yes) &&
tr.Abstain.Equal(comp.Abstain) &&
tr.No.Equal(comp.No) &&
tr.NoWithVeto.Equal(comp.NoWithVeto)
}
func (tr TallyResult) String() string {
return fmt.Sprintf(`Tally Result:
Yes: %s
Abstain: %s
No: %s
NoWithVeto: %s`, tr.Yes, tr.Abstain, tr.No, tr.NoWithVeto)
}
// ----------------------------------------------------------------------------
// Proposal
// ----------------------------------------------------------------------------
type Proposal struct {
ProposalContent `json:"proposal_content"` // Proposal content interface
ProposalID uint64 `json:"proposal_id"` // ID of the proposal
Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected}
FinalTallyResult TallyResult `json:"final_tally_result"` // Result of Tallys
SubmitTime time.Time `json:"submit_time"` // Time of the block where TxGovSubmitProposal was included
DepositEndTime time.Time `json:"deposit_end_time"` // Time that the Proposal would expire if deposit amount isn't met
TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit
VotingStartTime time.Time `json:"voting_start_time"` // Time of the block where MinDeposit was reached. -1 if MinDeposit is not reached
VotingEndTime time.Time `json:"voting_end_time"` // Time that the VotingPeriod for this proposal will end and votes will be tallied
}
// nolint
func (p Proposal) String() string {
return fmt.Sprintf(`Proposal %d:
Title: %s
Type: %s
Status: %s
Submit Time: %s
Deposit End Time: %s
Total Deposit: %s
Voting Start Time: %s
Voting End Time: %s
Description: %s`,
p.ProposalID, p.GetTitle(), p.ProposalType(),
p.Status, p.SubmitTime, p.DepositEndTime,
p.TotalDeposit, p.VotingStartTime, p.VotingEndTime, p.GetDescription(),
)
}
// ProposalContent is an interface that has title, description, and proposaltype
// that the governance module can use to identify them and generate human readable messages
// ProposalContent can have additional fields, which will handled by ProposalHandlers
// via type assertion, e.g. parameter change amount in ParameterChangeProposal
type ProposalContent interface {
GetTitle() string
GetDescription() string
ProposalType() ProposalKind
}
// Proposals is an array of proposal
type Proposals []Proposal
// nolint
func (p Proposals) String() string {
out := "ID - (Status) [Type] Title\n"
for _, prop := range p {
out += fmt.Sprintf("%d - (%s) [%s] %s\n",
prop.ProposalID, prop.Status,
prop.ProposalType(), prop.GetTitle())
}
return strings.TrimSpace(out)
}
// Text Proposals
type TextProposal struct {
Title string `json:"title"` // Title of the proposal
Description string `json:"description"` // Description of the proposal
}
func NewTextProposal(title, description string) TextProposal {
return TextProposal{
Title: title,
Description: description,
}
}
// Implements Proposal Interface
var _ ProposalContent = TextProposal{}
// nolint
func (tp TextProposal) GetTitle() string { return tp.Title }
func (tp TextProposal) GetDescription() string { return tp.Description }
func (tp TextProposal) ProposalType() ProposalKind { return ProposalTypeText }
// Software Upgrade Proposals
type SoftwareUpgradeProposal struct {
TextProposal
}
func NewSoftwareUpgradeProposal(title, description string) SoftwareUpgradeProposal {
return SoftwareUpgradeProposal{
TextProposal: NewTextProposal(title, description),
}
}
// Implements Proposal Interface
var _ ProposalContent = SoftwareUpgradeProposal{}
// nolint
func (sup SoftwareUpgradeProposal) ProposalType() ProposalKind { return ProposalTypeSoftwareUpgrade }
// ProposalQueue
type ProposalQueue []uint64
// ProposalKind
// Type that represents Proposal Type as a byte
type ProposalKind byte
//nolint
const (
ProposalTypeNil ProposalKind = 0x00
ProposalTypeText ProposalKind = 0x01
ProposalTypeParameterChange ProposalKind = 0x02
ProposalTypeSoftwareUpgrade ProposalKind = 0x03
)
// String to proposalType byte. Returns 0xff if invalid.
func ProposalTypeFromString(str string) (ProposalKind, error) {
switch str {
case "Text":
return ProposalTypeText, nil
case "ParameterChange":
return ProposalTypeParameterChange, nil
case "SoftwareUpgrade":
return ProposalTypeSoftwareUpgrade, nil
default:
return ProposalKind(0xff), fmt.Errorf("'%s' is not a valid proposal type", str)
}
}
// Marshal needed for protobuf compatibility
func (pt ProposalKind) Marshal() ([]byte, error) {
return []byte{byte(pt)}, nil
}
// Unmarshal needed for protobuf compatibility
func (pt *ProposalKind) Unmarshal(data []byte) error {
*pt = ProposalKind(data[0])
return nil
}
// Marshals to JSON using string
func (pt ProposalKind) MarshalJSON() ([]byte, error) {
return json.Marshal(pt.String())
}
// Unmarshals from JSON assuming Bech32 encoding
func (pt *ProposalKind) UnmarshalJSON(data []byte) error {
var s string
err := json.Unmarshal(data, &s)
if err != nil {
return err
}
bz2, err := ProposalTypeFromString(s)
if err != nil {
return err
}
*pt = bz2
return nil
}
// Turns VoteOption byte to String
func (pt ProposalKind) String() string {
switch pt {
case ProposalTypeText:
return "Text"
case ProposalTypeParameterChange:
return "ParameterChange"
case ProposalTypeSoftwareUpgrade:
return "SoftwareUpgrade"
default:
return ""
}
}
// For Printf / Sprintf, returns bech32 when using %s
// nolint: errcheck
func (pt ProposalKind) Format(s fmt.State, verb rune) {
switch verb {
case 's':
s.Write([]byte(pt.String()))
default:
// TODO: Do this conversion more directly
s.Write([]byte(fmt.Sprintf("%v", byte(pt))))
}
}
// ----------------------------------------------------------------------------
// Codec
// ----------------------------------------------------------------------------
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterInterface((*ProposalContent)(nil), nil)
cdc.RegisterConcrete(TextProposal{}, "gov/TextProposal", nil)
cdc.RegisterConcrete(SoftwareUpgradeProposal{}, "gov/SoftwareUpgradeProposal", nil)
}

View File

@ -0,0 +1,76 @@
package v036
import (
v034gov "github.com/cosmos/cosmos-sdk/x/gov/legacy/v034"
)
func MigrateGovernance(initialState v034gov.GenesisState) GenesisState {
targetGov := GenesisState{
StartingProposalID: initialState.StartingProposalID,
DepositParams: DepositParams{
MinDeposit: initialState.DepositParams.MinDeposit,
MaxDepositPeriod: initialState.DepositParams.MaxDepositPeriod,
},
TallyParams: TallyParams{
Quorum: initialState.TallyParams.Quorum,
Threshold: initialState.TallyParams.Threshold,
Veto: initialState.TallyParams.Veto,
},
VotingParams: VotingParams{
VotingPeriod: initialState.VotingParams.VotingPeriod,
},
}
var deposits Deposits
for _, p := range initialState.Deposits {
deposits = append(deposits, Deposit{
ProposalID: p.Deposit.ProposalID,
Amount: p.Deposit.Amount,
Depositor: p.Deposit.Depositor,
})
}
targetGov.Deposits = deposits
var votes Votes
for _, p := range initialState.Votes {
votes = append(votes, Vote{
ProposalID: p.Vote.ProposalID,
Option: VoteOption(p.Vote.Option),
Voter: p.Vote.Voter,
})
}
targetGov.Votes = votes
var proposals Proposals
for _, p := range initialState.Proposals {
proposal := Proposal{
Content: migrateContent(p.ProposalContent),
ProposalID: p.ProposalID,
Status: ProposalStatus(p.Status),
FinalTallyResult: TallyResult(p.FinalTallyResult),
SubmitTime: p.SubmitTime,
DepositEndTime: p.DepositEndTime,
TotalDeposit: p.TotalDeposit,
VotingStartTime: p.VotingStartTime,
VotingEndTime: p.VotingEndTime,
}
proposals = append(proposals, proposal)
}
targetGov.Proposals = proposals
return targetGov
}
func migrateContent(proposalContent v034gov.ProposalContent) (content Content) {
switch proposalContent.ProposalType() {
case v034gov.ProposalTypeText:
return NewTextProposal(proposalContent.GetTitle(), proposalContent.GetDescription())
case v034gov.ProposalTypeSoftwareUpgrade:
return NewSoftwareUpgradeProposal(proposalContent.GetTitle(), proposalContent.GetDescription())
default:
return nil
}
}

454
x/gov/legacy/v036/types.go Normal file
View File

@ -0,0 +1,454 @@
package v036
import (
"encoding/json"
"fmt"
"strings"
"time"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Keys
const (
// ModuleName is the name of the module
ModuleName = "gov"
// StoreKey is the store key string for gov
StoreKey = ModuleName
// RouterKey is the message route for gov
RouterKey = ModuleName
// QuerierRoute is the querier route for gov
QuerierRoute = ModuleName
// DefaultParamspace default name for parameter store
DefaultParamspace = ModuleName
)
// GenesisState represents v0.34.x genesis state for the governance module.
type GenesisState struct {
StartingProposalID uint64 `json:"starting_proposal_id"`
Deposits Deposits `json:"deposits"`
Votes Votes `json:"votes"`
Proposals []Proposal `json:"proposals"`
DepositParams DepositParams `json:"deposit_params"`
VotingParams VotingParams `json:"voting_params"`
TallyParams TallyParams `json:"tally_params"`
}
type Deposit struct {
ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal
Depositor sdk.AccAddress `json:"depositor"` // Address of the depositor
Amount sdk.Coins `json:"amount"` // Deposit amount
}
type Deposits []Deposit
type Vote struct {
ProposalID uint64 `json:"proposal_id"` // proposalID of the proposal
Voter sdk.AccAddress `json:"voter"` // address of the voter
Option VoteOption `json:"option"` // option from OptionSet chosen by the voter
}
type Votes []Vote
// Param around deposits for governance
type DepositParams struct {
MinDeposit sdk.Coins `json:"min_deposit,omitempty"` // Minimum deposit for a proposal to enter voting period.
MaxDepositPeriod time.Duration `json:"max_deposit_period,omitempty"` // Maximum period for Atom holders to deposit on a proposal. Initial value: 2 months
}
type Content interface {
GetTitle() string
GetDescription() string
ProposalRoute() string
ProposalType() string
ValidateBasic() sdk.Error
String() string
}
type Proposal struct {
Content `json:"content"` // Proposal content interface
ProposalID uint64 `json:"id"` // ID of the proposal
Status ProposalStatus `json:"proposal_status"` // Status of the Proposal {Pending, Active, Passed, Rejected}
FinalTallyResult TallyResult `json:"final_tally_result"` // Result of Tallys
SubmitTime time.Time `json:"submit_time"` // Time of the block where TxGovSubmitProposal was included
DepositEndTime time.Time `json:"deposit_end_time"` // Time that the Proposal would expire if deposit amount isn't met
TotalDeposit sdk.Coins `json:"total_deposit"` // Current deposit on this proposal. Initial value is set at InitialDeposit
VotingStartTime time.Time `json:"voting_start_time"` // Time of the block where MinDeposit was reached. -1 if MinDeposit is not reached
VotingEndTime time.Time `json:"voting_end_time"` // Time that the VotingPeriod for this proposal will end and votes will be tallied
}
type Proposals []Proposal
type ProposalQueue []uint64
type TallyParams struct {
Quorum sdk.Dec `json:"quorum,omitempty"` // Minimum percentage of total stake needed to vote for a result to be considered valid
Threshold sdk.Dec `json:"threshold,omitempty"` // Minimum proportion of Yes votes for proposal to pass. Initial value: 0.5
Veto sdk.Dec `json:"veto,omitempty"` // Minimum value of Veto votes to Total votes ratio for proposal to be vetoed. Initial value: 1/3
}
// ----------------------------------------------------------------------------
// ProposalStatus
// ----------------------------------------------------------------------------
// ProposalStatus is a type alias that represents a proposal status as a byte
type ProposalStatus byte
//nolint
const (
StatusNil ProposalStatus = 0x00
StatusDepositPeriod ProposalStatus = 0x01
StatusVotingPeriod ProposalStatus = 0x02
StatusPassed ProposalStatus = 0x03
StatusRejected ProposalStatus = 0x04
StatusFailed ProposalStatus = 0x05
)
// ProposalStatusToString turns a string into a ProposalStatus
func ProposalStatusFromString(str string) (ProposalStatus, error) {
switch str {
case "DepositPeriod":
return StatusDepositPeriod, nil
case "VotingPeriod":
return StatusVotingPeriod, nil
case "Passed":
return StatusPassed, nil
case "Rejected":
return StatusRejected, nil
case "Failed":
return StatusFailed, nil
case "":
return StatusNil, nil
default:
return ProposalStatus(0xff), fmt.Errorf("'%s' is not a valid proposal status", str)
}
}
// Marshal needed for protobuf compatibility
func (status ProposalStatus) Marshal() ([]byte, error) {
return []byte{byte(status)}, nil
}
// Unmarshal needed for protobuf compatibility
func (status *ProposalStatus) Unmarshal(data []byte) error {
*status = ProposalStatus(data[0])
return nil
}
// Marshals to JSON using string
func (status ProposalStatus) MarshalJSON() ([]byte, error) {
return json.Marshal(status.String())
}
// Unmarshals from JSON assuming Bech32 encoding
func (status *ProposalStatus) UnmarshalJSON(data []byte) error {
var s string
err := json.Unmarshal(data, &s)
if err != nil {
return err
}
bz2, err := ProposalStatusFromString(s)
if err != nil {
return err
}
*status = bz2
return nil
}
// String implements the Stringer interface.
func (status ProposalStatus) String() string {
switch status {
case StatusDepositPeriod:
return "DepositPeriod"
case StatusVotingPeriod:
return "VotingPeriod"
case StatusPassed:
return "Passed"
case StatusRejected:
return "Rejected"
case StatusFailed:
return "Failed"
default:
return ""
}
}
// Format implements the fmt.Formatter interface.
// nolint: errcheck
func (status ProposalStatus) Format(s fmt.State, verb rune) {
switch verb {
case 's':
s.Write([]byte(status.String()))
default:
// TODO: Do this conversion more directly
s.Write([]byte(fmt.Sprintf("%v", byte(status))))
}
}
type VotingParams struct {
VotingPeriod time.Duration `json:"voting_period,omitempty"` // Length of the voting period.
}
type TallyResult struct {
Yes sdk.Int `json:"yes"`
Abstain sdk.Int `json:"abstain"`
No sdk.Int `json:"no"`
NoWithVeto sdk.Int `json:"no_with_veto"`
}
// ----------------------------------------------------------------------------
// VoteOption
// ----------------------------------------------------------------------------
type VoteOption byte
// Vote options
const (
OptionEmpty VoteOption = 0x00
OptionYes VoteOption = 0x01
OptionAbstain VoteOption = 0x02
OptionNo VoteOption = 0x03
OptionNoWithVeto VoteOption = 0x04
)
// VoteOptionFromString returns a VoteOption from a string. It returns an error
// if the string is invalid.
func VoteOptionFromString(str string) (VoteOption, error) {
switch str {
case "Yes":
return OptionYes, nil
case "Abstain":
return OptionAbstain, nil
case "No":
return OptionNo, nil
case "NoWithVeto":
return OptionNoWithVeto, nil
default:
return VoteOption(0xff), fmt.Errorf("'%s' is not a valid vote option", str)
}
}
// Marshal needed for protobuf compatibility.
func (vo VoteOption) Marshal() ([]byte, error) {
return []byte{byte(vo)}, nil
}
// Unmarshal needed for protobuf compatibility.
func (vo *VoteOption) Unmarshal(data []byte) error {
*vo = VoteOption(data[0])
return nil
}
// Marshals to JSON using string.
func (vo VoteOption) MarshalJSON() ([]byte, error) {
return json.Marshal(vo.String())
}
// UnmarshalJSON decodes from JSON assuming Bech32 encoding.
func (vo *VoteOption) UnmarshalJSON(data []byte) error {
var s string
err := json.Unmarshal(data, &s)
if err != nil {
return err
}
bz2, err := VoteOptionFromString(s)
if err != nil {
return err
}
*vo = bz2
return nil
}
// String implements the Stringer interface.
func (vo VoteOption) String() string {
switch vo {
case OptionYes:
return "Yes"
case OptionAbstain:
return "Abstain"
case OptionNo:
return "No"
case OptionNoWithVeto:
return "NoWithVeto"
default:
return ""
}
}
// Format implements the fmt.Formatter interface.
// nolint: errcheck
func (vo VoteOption) Format(s fmt.State, verb rune) {
switch verb {
case 's':
s.Write([]byte(vo.String()))
default:
s.Write([]byte(fmt.Sprintf("%v", byte(vo))))
}
}
// ----------------------------------------------------------------------------
// Tally
// ----------------------------------------------------------------------------
// Equals returns if two proposals are equal.
func (tr TallyResult) Equals(comp TallyResult) bool {
return tr.Yes.Equal(comp.Yes) &&
tr.Abstain.Equal(comp.Abstain) &&
tr.No.Equal(comp.No) &&
tr.NoWithVeto.Equal(comp.NoWithVeto)
}
func (tr TallyResult) String() string {
return fmt.Sprintf(`Tally Result:
Yes: %s
Abstain: %s
No: %s
NoWithVeto: %s`, tr.Yes, tr.Abstain, tr.No, tr.NoWithVeto)
}
// ----------------------------------------------------------------------------
// Proposal
// ----------------------------------------------------------------------------
// Proposal types
const (
ProposalTypeText string = "Text"
ProposalTypeSoftwareUpgrade string = "SoftwareUpgrade"
)
// Text Proposal
type TextProposal struct {
Title string `json:"title"`
Description string `json:"description"`
}
func NewTextProposal(title, description string) Content {
return TextProposal{title, description}
}
// Implements Proposal Interface
var _ Content = TextProposal{}
// nolint
func (tp TextProposal) GetTitle() string { return tp.Title }
func (tp TextProposal) GetDescription() string { return tp.Description }
func (tp TextProposal) ProposalRoute() string { return RouterKey }
func (tp TextProposal) ProposalType() string { return ProposalTypeText }
func (tp TextProposal) ValidateBasic() sdk.Error { return ValidateAbstract(DefaultCodespace, tp) }
const (
DefaultCodespace sdk.CodespaceType = "gov"
CodeInvalidContent sdk.CodeType = 6
CodeInvalidProposalType sdk.CodeType = 7
)
func ErrInvalidProposalContent(cs sdk.CodespaceType, msg string) sdk.Error {
return sdk.NewError(cs, CodeInvalidContent, fmt.Sprintf("invalid proposal content: %s", msg))
}
func ErrInvalidProposalType(codespace sdk.CodespaceType, proposalType string) sdk.Error {
return sdk.NewError(codespace, CodeInvalidProposalType, fmt.Sprintf("proposal type '%s' is not valid", proposalType))
}
// ValidateAbstract validates a proposal's abstract contents returning an error
// if invalid.
func ValidateAbstract(codespace sdk.CodespaceType, c Content) sdk.Error {
title := c.GetTitle()
if len(strings.TrimSpace(title)) == 0 {
return ErrInvalidProposalContent(codespace, "proposal title cannot be blank")
}
if len(title) > MaxTitleLength {
return ErrInvalidProposalContent(codespace, fmt.Sprintf("proposal title is longer than max length of %d", MaxTitleLength))
}
description := c.GetDescription()
if len(description) == 0 {
return ErrInvalidProposalContent(codespace, "proposal description cannot be blank")
}
if len(description) > MaxDescriptionLength {
return ErrInvalidProposalContent(codespace, fmt.Sprintf("proposal description is longer than max length of %d", MaxDescriptionLength))
}
return nil
}
// Constants pertaining to a Content object
const (
MaxDescriptionLength int = 5000
MaxTitleLength int = 140
)
func (tp TextProposal) String() string {
return fmt.Sprintf(`Text Proposal:
Title: %s
Description: %s
`, tp.Title, tp.Description)
}
// Software Upgrade Proposals
// TODO: We have to add fields for SUP specific arguments e.g. commit hash,
// upgrade date, etc.
type SoftwareUpgradeProposal struct {
Title string `json:"title"`
Description string `json:"description"`
}
func NewSoftwareUpgradeProposal(title, description string) Content {
return SoftwareUpgradeProposal{title, description}
}
// Implements Proposal Interface
var _ Content = SoftwareUpgradeProposal{}
// nolint
func (sup SoftwareUpgradeProposal) GetTitle() string { return sup.Title }
func (sup SoftwareUpgradeProposal) GetDescription() string { return sup.Description }
func (sup SoftwareUpgradeProposal) ProposalRoute() string { return RouterKey }
func (sup SoftwareUpgradeProposal) ProposalType() string { return ProposalTypeSoftwareUpgrade }
func (sup SoftwareUpgradeProposal) ValidateBasic() sdk.Error {
return ValidateAbstract(DefaultCodespace, sup)
}
func (sup SoftwareUpgradeProposal) String() string {
return fmt.Sprintf(`Software Upgrade Proposal:
Title: %s
Description: %s
`, sup.Title, sup.Description)
}
// ----------------------------------------------------------------------------
// Codec
// ----------------------------------------------------------------------------
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterInterface((*Content)(nil), nil)
cdc.RegisterConcrete(TextProposal{}, "cosmos-sdk/TextProposal", nil)
cdc.RegisterConcrete(SoftwareUpgradeProposal{}, "cosmos-sdk/SoftwareUpgradeProposal", nil)
}