add genesis init/export/validation

This commit is contained in:
rhuairahrighairigh 2020-03-15 00:00:05 +00:00
parent 62da823314
commit 18dfcd2a3d
4 changed files with 215 additions and 11 deletions

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/committee/types"
) )
// InitGenesis initializes the store state from a genesis state. // InitGenesis initializes the store state from a genesis state.
@ -14,11 +15,44 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, gs GenesisState) {
keeper.SetNextProposalID(ctx, gs.NextProposalID) keeper.SetNextProposalID(ctx, gs.NextProposalID)
// TODO set votes, committee, proposals for _, com := range gs.Committees {
keeper.SetCommittee(ctx, com)
}
for _, p := range gs.Proposals {
keeper.SetProposal(ctx, p)
}
for _, v := range gs.Votes {
keeper.SetVote(ctx, v)
}
} }
// ExportGenesis returns a GenesisState for a given context and keeper. // ExportGenesis returns a GenesisState for a given context and keeper.
func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState { func ExportGenesis(ctx sdk.Context, keeper Keeper) GenesisState {
// TODO
return GenesisState{} nextID, err := keeper.GetNextProposalID(ctx)
if err != nil {
panic(err)
}
committees := []types.Committee{}
keeper.IterateCommittees(ctx, func(com types.Committee) bool {
committees = append(committees, com)
return false
})
proposals := []types.Proposal{}
votes := []types.Vote{}
keeper.IterateProposals(ctx, func(p types.Proposal) bool {
proposals = append(proposals, p)
keeper.IterateVotes(ctx, p.ID, func(v types.Vote) bool {
votes = append(votes, v)
return false
})
return false
})
return types.NewGenesisState(
nextID,
committees,
proposals,
votes,
)
} }

View File

@ -0,0 +1,75 @@
package committee_test
import (
"testing"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/app"
"github.com/kava-labs/kava/x/committee"
"github.com/kava-labs/kava/x/committee/types"
)
type GenesisTestSuite struct {
suite.Suite
app app.TestApp
ctx sdk.Context
keeper committee.Keeper
}
func (suite *GenesisTestSuite) TestGenesis() {
testCases := []struct {
name string
genState types.GenesisState
expectPass bool
}{
{
name: "normal",
genState: types.DefaultGenesisState(),
expectPass: true,
},
{
name: "invalid",
genState: types.NewGenesisState(
2,
[]types.Committee{},
[]types.Proposal{{ID: 1, CommitteeID: 57}},
[]types.Vote{},
),
expectPass: false,
},
}
for _, tc := range testCases {
suite.Run(tc.name, func() {
// Setup (note: suite.SetupTest is not run before every suite.Run)
suite.app = app.NewTestApp()
suite.keeper = suite.app.GetCommitteeKeeper()
suite.ctx = suite.app.NewContext(true, abci.Header{})
// Run
var exportedGenState types.GenesisState
run := func() {
committee.InitGenesis(suite.ctx, suite.keeper, tc.genState)
exportedGenState = committee.ExportGenesis(suite.ctx, suite.keeper)
}
if tc.expectPass {
suite.NotPanics(run)
} else {
suite.Panics(run)
}
// Check
if tc.expectPass {
suite.Equal(tc.genState, exportedGenState)
}
})
}
}
func TestGenesisTestSuite(t *testing.T) {
suite.Run(t, new(GenesisTestSuite))
}

View File

@ -2,6 +2,7 @@ package types
import ( import (
"bytes" "bytes"
"fmt"
) )
// DefaultNextProposalID is the starting poiint for proposal IDs. // DefaultNextProposalID is the starting poiint for proposal IDs.
@ -10,18 +11,18 @@ const DefaultNextProposalID uint64 = 1
// GenesisState is state that must be provided at chain genesis. // GenesisState is state that must be provided at chain genesis.
type GenesisState struct { type GenesisState struct {
NextProposalID uint64 NextProposalID uint64
Votes []Vote
Proposals []Proposal
Committees []Committee Committees []Committee
Proposals []Proposal
Votes []Vote
} }
// NewGenesisState returns a new genesis state object for the module. // NewGenesisState returns a new genesis state object for the module.
func NewGenesisState(nextProposalID uint64, votes []Vote, proposals []Proposal, committees []Committee) GenesisState { func NewGenesisState(nextProposalID uint64, committees []Committee, proposals []Proposal, votes []Vote) GenesisState {
return GenesisState{ return GenesisState{
NextProposalID: nextProposalID, NextProposalID: nextProposalID,
Votes: votes,
Proposals: proposals,
Committees: committees, Committees: committees,
Proposals: proposals,
Votes: votes,
} }
} }
@ -29,9 +30,9 @@ func NewGenesisState(nextProposalID uint64, votes []Vote, proposals []Proposal,
func DefaultGenesisState() GenesisState { func DefaultGenesisState() GenesisState {
return NewGenesisState( return NewGenesisState(
DefaultNextProposalID, DefaultNextProposalID,
[]Vote{},
[]Proposal{},
[]Committee{}, []Committee{},
[]Proposal{},
[]Vote{},
) )
} }
@ -48,4 +49,62 @@ func (data GenesisState) IsEmpty() bool {
} }
// Validate performs basic validation of genesis data. // Validate performs basic validation of genesis data.
func (gs GenesisState) Validate() error { return nil } func (gs GenesisState) Validate() error {
// validate committees
committeeMap := make(map[uint64]bool, len(gs.Committees))
for _, com := range gs.Committees {
// check there are no duplicate IDs
if _, ok := committeeMap[com.ID]; ok {
return fmt.Errorf("duplicate committee ID found in genesis state; id: %d", com.ID)
}
committeeMap[com.ID] = true
// validate committee
if len(com.Members) == 0 {
return fmt.Errorf("committee %d invalid: cannot have zero members", com.ID)
}
for _, m := range com.Members {
if m.Empty() {
return fmt.Errorf("committee %d invalid: found empty member address", com.ID)
}
}
}
// validate proposals - pp.Val, no duplicate IDs, no ids >= nextID, committee needs to exist
proposalMap := make(map[uint64]bool, len(gs.Proposals))
for _, p := range gs.Proposals {
// check there are no duplicate IDs
if _, ok := proposalMap[p.ID]; ok {
return fmt.Errorf("duplicate proposal ID found in genesis state; id: %d", p.ID)
}
proposalMap[p.ID] = true
// validate next proposal ID
if p.ID >= gs.NextProposalID {
return fmt.Errorf("NextProposalID is not greater than all proposal IDs; id: %d", p.ID)
}
// check committee exists
if !committeeMap[p.CommitteeID] {
return fmt.Errorf("proposal refers to non existant committee; proposal: %+v", p)
}
// validate pubProposal
if err := p.PubProposal.ValidateBasic(); err != nil {
return fmt.Errorf("proposal %d invalid: %w", p.ID, err)
}
}
// validate votes
for _, v := range gs.Votes {
// check proposal exists
if !proposalMap[v.ProposalID] {
return fmt.Errorf("vote refers to non existant proposal; vote: %+v", v)
}
// validate address
if v.Voter.Empty() {
return fmt.Errorf("found empty voter address; vote: %+v", v)
}
}
return nil
}

View File

@ -0,0 +1,36 @@
package types
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestGenesisState_Validate(t *testing.T) {
testCases := []struct {
name string
genState GenesisState
expectPass bool
}{
{
name: "normal",
genState: DefaultGenesisState(),
expectPass: true,
},
// TODO test failure cases
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
err := tc.genState.Validate()
if tc.expectPass {
require.NoError(t, err)
} else {
require.Error(t, err)
}
})
}
}