cosmos-sdk/x/group/keeper/keeper.go
Jacob Gadikian b7097c3b11
chore!: var-naming linter errors (#12135)
## Description

This PR works towards #12133 and does a fumpt for consistency once sdk.Int is merged.

The suggested merge order is to begin with sdk.Int, merge in the various linter PR's one by one, and then finally merge the changes to CI from #12134 

---

### Author Checklist

*All items are required. Please add a note to the item if the item is not applicable and
please add links to any relevant follow up issues.*

I have...

- [x] included the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [x] added `!` to the type prefix if API or client breaking change
- [x] targeted the correct branch (see [PR Targeting](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#pr-targeting))
- [x] provided a link to the relevant issue or specification
- [ ] followed the guidelines for [building modules](https://github.com/cosmos/cosmos-sdk/blob/main/docs/building-modules)
- [ ] included the necessary unit and integration [tests](https://github.com/cosmos/cosmos-sdk/blob/main/CONTRIBUTING.md#testing)
- [ ] added a changelog entry to `CHANGELOG.md`
- [ ] included comments for [documenting Go code](https://blog.golang.org/godoc)
- [ ] updated the relevant documentation or specification
- [x] reviewed "Files changed" and left comments if necessary
- [x] confirmed all CI checks have passed

### Reviewers Checklist

*All items are required. Please add a note if the item is not applicable and please add
your handle next to the items reviewed if you only reviewed selected items.*

I have...

- [ ] confirmed the correct [type prefix](https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json) in the PR title
- [ ] confirmed `!` in the type prefix if API or client breaking change
- [ ] confirmed all author checklist items have been addressed 
- [ ] reviewed state machine logic
- [ ] reviewed API design and naming
- [ ] reviewed documentation is accurate
- [ ] reviewed tests and test coverage
- [ ] manually tested (if applicable)
2022-06-03 13:19:54 +00:00

394 lines
12 KiB
Go

package keeper
import (
"fmt"
"time"
"github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/group"
"github.com/cosmos/cosmos-sdk/x/group/errors"
"github.com/cosmos/cosmos-sdk/x/group/internal/orm"
)
const (
// Group Table
GroupTablePrefix byte = 0x0
GroupTableSeqPrefix byte = 0x1
GroupByAdminIndexPrefix byte = 0x2
// Group Member Table
GroupMemberTablePrefix byte = 0x10
GroupMemberByGroupIndexPrefix byte = 0x11
GroupMemberByMemberIndexPrefix byte = 0x12
// Group Policy Table
GroupPolicyTablePrefix byte = 0x20
GroupPolicyTableSeqPrefix byte = 0x21
GroupPolicyByGroupIndexPrefix byte = 0x22
GroupPolicyByAdminIndexPrefix byte = 0x23
// Proposal Table
ProposalTablePrefix byte = 0x30
ProposalTableSeqPrefix byte = 0x31
ProposalByGroupPolicyIndexPrefix byte = 0x32
ProposalsByVotingPeriodEndPrefix byte = 0x33
// Vote Table
VoteTablePrefix byte = 0x40
VoteByProposalIndexPrefix byte = 0x41
VoteByVoterIndexPrefix byte = 0x42
)
type Keeper struct {
key storetypes.StoreKey
accKeeper group.AccountKeeper
// Group Table
groupTable orm.AutoUInt64Table
groupByAdminIndex orm.Index
// Group Member Table
groupMemberTable orm.PrimaryKeyTable
groupMemberByGroupIndex orm.Index
groupMemberByMemberIndex orm.Index
// Group Policy Table
groupPolicySeq orm.Sequence
groupPolicyTable orm.PrimaryKeyTable
groupPolicyByGroupIndex orm.Index
groupPolicyByAdminIndex orm.Index
// Proposal Table
proposalTable orm.AutoUInt64Table
proposalByGroupPolicyIndex orm.Index
proposalsByVotingPeriodEnd orm.Index
// Vote Table
voteTable orm.PrimaryKeyTable
voteByProposalIndex orm.Index
voteByVoterIndex orm.Index
router *baseapp.MsgServiceRouter
config group.Config
}
// NewKeeper creates a new group keeper.
func NewKeeper(storeKey storetypes.StoreKey, cdc codec.Codec, router *baseapp.MsgServiceRouter, accKeeper group.AccountKeeper, config group.Config) Keeper {
k := Keeper{
key: storeKey,
router: router,
accKeeper: accKeeper,
}
groupTable, err := orm.NewAutoUInt64Table([2]byte{GroupTablePrefix}, GroupTableSeqPrefix, &group.GroupInfo{}, cdc)
if err != nil {
panic(err.Error())
}
k.groupByAdminIndex, err = orm.NewIndex(groupTable, GroupByAdminIndexPrefix, func(val interface{}) ([]interface{}, error) {
addr, err := sdk.AccAddressFromBech32(val.(*group.GroupInfo).Admin)
if err != nil {
return nil, err
}
return []interface{}{addr.Bytes()}, nil
}, []byte{})
if err != nil {
panic(err.Error())
}
k.groupTable = *groupTable
// Group Member Table
groupMemberTable, err := orm.NewPrimaryKeyTable([2]byte{GroupMemberTablePrefix}, &group.GroupMember{}, cdc)
if err != nil {
panic(err.Error())
}
k.groupMemberByGroupIndex, err = orm.NewIndex(groupMemberTable, GroupMemberByGroupIndexPrefix, func(val interface{}) ([]interface{}, error) {
group := val.(*group.GroupMember).GroupId
return []interface{}{group}, nil
}, group.GroupMember{}.GroupId)
if err != nil {
panic(err.Error())
}
k.groupMemberByMemberIndex, err = orm.NewIndex(groupMemberTable, GroupMemberByMemberIndexPrefix, func(val interface{}) ([]interface{}, error) {
memberAddr := val.(*group.GroupMember).Member.Address
addr, err := sdk.AccAddressFromBech32(memberAddr)
if err != nil {
return nil, err
}
return []interface{}{addr.Bytes()}, nil
}, []byte{})
if err != nil {
panic(err.Error())
}
k.groupMemberTable = *groupMemberTable
// Group Policy Table
k.groupPolicySeq = orm.NewSequence(GroupPolicyTableSeqPrefix)
groupPolicyTable, err := orm.NewPrimaryKeyTable([2]byte{GroupPolicyTablePrefix}, &group.GroupPolicyInfo{}, cdc)
if err != nil {
panic(err.Error())
}
k.groupPolicyByGroupIndex, err = orm.NewIndex(groupPolicyTable, GroupPolicyByGroupIndexPrefix, func(value interface{}) ([]interface{}, error) {
return []interface{}{value.(*group.GroupPolicyInfo).GroupId}, nil
}, group.GroupPolicyInfo{}.GroupId)
if err != nil {
panic(err.Error())
}
k.groupPolicyByAdminIndex, err = orm.NewIndex(groupPolicyTable, GroupPolicyByAdminIndexPrefix, func(value interface{}) ([]interface{}, error) {
admin := value.(*group.GroupPolicyInfo).Admin
addr, err := sdk.AccAddressFromBech32(admin)
if err != nil {
return nil, err
}
return []interface{}{addr.Bytes()}, nil
}, []byte{})
if err != nil {
panic(err.Error())
}
k.groupPolicyTable = *groupPolicyTable
// Proposal Table
proposalTable, err := orm.NewAutoUInt64Table([2]byte{ProposalTablePrefix}, ProposalTableSeqPrefix, &group.Proposal{}, cdc)
if err != nil {
panic(err.Error())
}
k.proposalByGroupPolicyIndex, err = orm.NewIndex(proposalTable, ProposalByGroupPolicyIndexPrefix, func(value interface{}) ([]interface{}, error) {
account := value.(*group.Proposal).GroupPolicyAddress
addr, err := sdk.AccAddressFromBech32(account)
if err != nil {
return nil, err
}
return []interface{}{addr.Bytes()}, nil
}, []byte{})
if err != nil {
panic(err.Error())
}
k.proposalsByVotingPeriodEnd, err = orm.NewIndex(proposalTable, ProposalsByVotingPeriodEndPrefix, func(value interface{}) ([]interface{}, error) {
votingPeriodEnd := value.(*group.Proposal).VotingPeriodEnd
return []interface{}{sdk.FormatTimeBytes(votingPeriodEnd)}, nil
}, []byte{})
if err != nil {
panic(err.Error())
}
k.proposalTable = *proposalTable
// Vote Table
voteTable, err := orm.NewPrimaryKeyTable([2]byte{VoteTablePrefix}, &group.Vote{}, cdc)
if err != nil {
panic(err.Error())
}
k.voteByProposalIndex, err = orm.NewIndex(voteTable, VoteByProposalIndexPrefix, func(value interface{}) ([]interface{}, error) {
return []interface{}{value.(*group.Vote).ProposalId}, nil
}, group.Vote{}.ProposalId)
if err != nil {
panic(err.Error())
}
k.voteByVoterIndex, err = orm.NewIndex(voteTable, VoteByVoterIndexPrefix, func(value interface{}) ([]interface{}, error) {
addr, err := sdk.AccAddressFromBech32(value.(*group.Vote).Voter)
if err != nil {
return nil, err
}
return []interface{}{addr.Bytes()}, nil
}, []byte{})
if err != nil {
panic(err.Error())
}
k.voteTable = *voteTable
if config.MaxMetadataLen == 0 {
config.MaxMetadataLen = group.DefaultConfig().MaxMetadataLen
}
if config.MaxExecutionPeriod == 0 {
config.MaxExecutionPeriod = group.DefaultConfig().MaxExecutionPeriod
}
k.config = config
return k
}
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", group.ModuleName))
}
// GetGroupSequence returns the current value of the group table sequence
func (k Keeper) GetGroupSequence(ctx sdk.Context) uint64 {
return k.groupTable.Sequence().CurVal(ctx.KVStore(k.key))
}
// iterateProposalsByVPEnd iterates over all proposals whose voting_period_end is after the `endTime` time argument.
func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, endTime time.Time, cb func(proposal group.Proposal) (bool, error)) error {
timeBytes := sdk.FormatTimeBytes(endTime)
it, err := k.proposalsByVotingPeriodEnd.PrefixScan(ctx.KVStore(k.key), nil, timeBytes)
if err != nil {
return err
}
defer it.Close()
for {
// Important: this following line cannot be outside of the for loop.
// It seems that when one unmarshals into the same `group.Proposal`
// reference, then gogoproto somehow "adds" the new bytes to the old
// object for some fields. When running simulations, for proposals with
// each 1-2 proposers, after a couple of loop iterations we got to a
// proposal with 60k+ proposers.
// So we're declaring a local variable that gets GCed.
//
// Also see `x/group/types/proposal_test.go`, TestGogoUnmarshalProposal().
var proposal group.Proposal
_, err := it.LoadNext(&proposal)
if errors.ErrORMIteratorDone.Is(err) {
break
}
if err != nil {
return err
}
stop, err := cb(proposal)
if err != nil {
return err
}
if stop {
break
}
}
return nil
}
// pruneProposal deletes a proposal from state.
func (k Keeper) pruneProposal(ctx sdk.Context, proposalID uint64) error {
store := ctx.KVStore(k.key)
err := k.proposalTable.Delete(store, proposalID)
if err != nil {
return err
}
k.Logger(ctx).Debug(fmt.Sprintf("Pruned proposal %d", proposalID))
return nil
}
// abortProposals iterates through all proposals by group policy index
// and marks submitted proposals as aborted.
func (k Keeper) abortProposals(ctx sdk.Context, groupPolicyAddr sdk.AccAddress) error {
proposalIt, err := k.proposalByGroupPolicyIndex.Get(ctx.KVStore(k.key), groupPolicyAddr.Bytes())
if err != nil {
return err
}
defer proposalIt.Close()
for {
var proposalInfo group.Proposal
_, err = proposalIt.LoadNext(&proposalInfo)
if errors.ErrORMIteratorDone.Is(err) {
break
}
if err != nil {
return err
}
// Mark all proposals still in the voting phase as aborted.
if proposalInfo.Status == group.PROPOSAL_STATUS_SUBMITTED {
proposalInfo.Status = group.PROPOSAL_STATUS_ABORTED
if err := k.proposalTable.Update(ctx.KVStore(k.key), proposalInfo.Id, &proposalInfo); err != nil {
return err
}
}
}
return nil
}
// pruneVotes prunes all votes for a proposal from state.
func (k Keeper) pruneVotes(ctx sdk.Context, proposalID uint64) error {
store := ctx.KVStore(k.key)
it, err := k.voteByProposalIndex.Get(store, proposalID)
if err != nil {
return err
}
defer it.Close()
for {
var vote group.Vote
_, err = it.LoadNext(&vote)
if errors.ErrORMIteratorDone.Is(err) {
break
}
if err != nil {
return err
}
err = k.voteTable.Delete(store, &vote)
if err != nil {
return err
}
}
return nil
}
// PruneProposals prunes all proposals that are expired, i.e. whose
// `voting_period + max_execution_period` is greater than the current block
// time.
func (k Keeper) PruneProposals(ctx sdk.Context) error {
err := k.iterateProposalsByVPEnd(ctx, ctx.BlockTime().Add(-k.config.MaxExecutionPeriod), func(proposal group.Proposal) (bool, error) {
err := k.pruneProposal(ctx, proposal.Id)
if err != nil {
return true, err
}
return false, nil
})
if err != nil {
return err
}
return nil
}
// TallyProposalsAtVPEnd iterates over all proposals whose voting period
// has ended, tallies their votes, prunes them, and updates the proposal's
// `FinalTallyResult` field.
func (k Keeper) TallyProposalsAtVPEnd(ctx sdk.Context) error {
return k.iterateProposalsByVPEnd(ctx, ctx.BlockTime(), func(proposal group.Proposal) (bool, error) {
policyInfo, err := k.getGroupPolicyInfo(ctx, proposal.GroupPolicyAddress)
if err != nil {
return true, sdkerrors.Wrap(err, "group policy")
}
electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId)
if err != nil {
return true, sdkerrors.Wrap(err, "group")
}
proposalID := proposal.Id
if proposal.Status == group.PROPOSAL_STATUS_ABORTED || proposal.Status == group.PROPOSAL_STATUS_WITHDRAWN {
if err := k.pruneProposal(ctx, proposalID); err != nil {
return true, err
}
if err := k.pruneVotes(ctx, proposalID); err != nil {
return true, err
}
} else {
err = k.doTallyAndUpdate(ctx, &proposal, electorate, policyInfo)
if err != nil {
return true, sdkerrors.Wrap(err, "doTallyAndUpdate")
}
if err := k.proposalTable.Update(ctx.KVStore(k.key), proposal.Id, &proposal); err != nil {
return true, sdkerrors.Wrap(err, "proposal update")
}
}
return false, nil
})
}