github.com/cosmos/cosmos-sdk@v0.50.10/x/group/keeper/keeper.go (about)

     1  package keeper
     2  
     3  import (
     4  	"fmt"
     5  	"time"
     6  
     7  	errorsmod "cosmossdk.io/errors"
     8  	"cosmossdk.io/log"
     9  	storetypes "cosmossdk.io/store/types"
    10  
    11  	"github.com/cosmos/cosmos-sdk/baseapp"
    12  	"github.com/cosmos/cosmos-sdk/codec"
    13  	sdk "github.com/cosmos/cosmos-sdk/types"
    14  	"github.com/cosmos/cosmos-sdk/x/group"
    15  	"github.com/cosmos/cosmos-sdk/x/group/errors"
    16  	"github.com/cosmos/cosmos-sdk/x/group/internal/orm"
    17  )
    18  
    19  const (
    20  	// Group Table
    21  	GroupTablePrefix        byte = 0x0
    22  	GroupTableSeqPrefix     byte = 0x1
    23  	GroupByAdminIndexPrefix byte = 0x2
    24  
    25  	// Group Member Table
    26  	GroupMemberTablePrefix         byte = 0x10
    27  	GroupMemberByGroupIndexPrefix  byte = 0x11
    28  	GroupMemberByMemberIndexPrefix byte = 0x12
    29  
    30  	// Group Policy Table
    31  	GroupPolicyTablePrefix        byte = 0x20
    32  	GroupPolicyTableSeqPrefix     byte = 0x21
    33  	GroupPolicyByGroupIndexPrefix byte = 0x22
    34  	GroupPolicyByAdminIndexPrefix byte = 0x23
    35  
    36  	// Proposal Table
    37  	ProposalTablePrefix              byte = 0x30
    38  	ProposalTableSeqPrefix           byte = 0x31
    39  	ProposalByGroupPolicyIndexPrefix byte = 0x32
    40  	ProposalsByVotingPeriodEndPrefix byte = 0x33
    41  
    42  	// Vote Table
    43  	VoteTablePrefix           byte = 0x40
    44  	VoteByProposalIndexPrefix byte = 0x41
    45  	VoteByVoterIndexPrefix    byte = 0x42
    46  )
    47  
    48  type Keeper struct {
    49  	key storetypes.StoreKey
    50  
    51  	accKeeper group.AccountKeeper
    52  
    53  	// Group Table
    54  	groupTable        orm.AutoUInt64Table
    55  	groupByAdminIndex orm.Index
    56  
    57  	// Group Member Table
    58  	groupMemberTable         orm.PrimaryKeyTable
    59  	groupMemberByGroupIndex  orm.Index
    60  	groupMemberByMemberIndex orm.Index
    61  
    62  	// Group Policy Table
    63  	groupPolicySeq          orm.Sequence
    64  	groupPolicyTable        orm.PrimaryKeyTable
    65  	groupPolicyByGroupIndex orm.Index
    66  	groupPolicyByAdminIndex orm.Index
    67  
    68  	// Proposal Table
    69  	proposalTable              orm.AutoUInt64Table
    70  	proposalByGroupPolicyIndex orm.Index
    71  	proposalsByVotingPeriodEnd orm.Index
    72  
    73  	// Vote Table
    74  	voteTable           orm.PrimaryKeyTable
    75  	voteByProposalIndex orm.Index
    76  	voteByVoterIndex    orm.Index
    77  
    78  	router baseapp.MessageRouter
    79  
    80  	config group.Config
    81  
    82  	cdc codec.Codec
    83  }
    84  
    85  // NewKeeper creates a new group keeper.
    86  func NewKeeper(storeKey storetypes.StoreKey, cdc codec.Codec, router baseapp.MessageRouter, accKeeper group.AccountKeeper, config group.Config) Keeper {
    87  	k := Keeper{
    88  		key:       storeKey,
    89  		router:    router,
    90  		accKeeper: accKeeper,
    91  		cdc:       cdc,
    92  	}
    93  
    94  	groupTable, err := orm.NewAutoUInt64Table([2]byte{GroupTablePrefix}, GroupTableSeqPrefix, &group.GroupInfo{}, cdc)
    95  	if err != nil {
    96  		panic(err.Error())
    97  	}
    98  	k.groupByAdminIndex, err = orm.NewIndex(groupTable, GroupByAdminIndexPrefix, func(val interface{}) ([]interface{}, error) {
    99  		addr, err := accKeeper.AddressCodec().StringToBytes(val.(*group.GroupInfo).Admin)
   100  		if err != nil {
   101  			return nil, err
   102  		}
   103  		return []interface{}{addr}, nil
   104  	}, []byte{})
   105  	if err != nil {
   106  		panic(err.Error())
   107  	}
   108  	k.groupTable = *groupTable
   109  
   110  	// Group Member Table
   111  	groupMemberTable, err := orm.NewPrimaryKeyTable([2]byte{GroupMemberTablePrefix}, &group.GroupMember{}, cdc)
   112  	if err != nil {
   113  		panic(err.Error())
   114  	}
   115  	k.groupMemberByGroupIndex, err = orm.NewIndex(groupMemberTable, GroupMemberByGroupIndexPrefix, func(val interface{}) ([]interface{}, error) {
   116  		group := val.(*group.GroupMember).GroupId
   117  		return []interface{}{group}, nil
   118  	}, group.GroupMember{}.GroupId)
   119  	if err != nil {
   120  		panic(err.Error())
   121  	}
   122  	k.groupMemberByMemberIndex, err = orm.NewIndex(groupMemberTable, GroupMemberByMemberIndexPrefix, func(val interface{}) ([]interface{}, error) {
   123  		memberAddr := val.(*group.GroupMember).Member.Address
   124  		addr, err := accKeeper.AddressCodec().StringToBytes(memberAddr)
   125  		if err != nil {
   126  			return nil, err
   127  		}
   128  		return []interface{}{addr}, nil
   129  	}, []byte{})
   130  	if err != nil {
   131  		panic(err.Error())
   132  	}
   133  	k.groupMemberTable = *groupMemberTable
   134  
   135  	// Group Policy Table
   136  	k.groupPolicySeq = orm.NewSequence(GroupPolicyTableSeqPrefix)
   137  	groupPolicyTable, err := orm.NewPrimaryKeyTable([2]byte{GroupPolicyTablePrefix}, &group.GroupPolicyInfo{}, cdc)
   138  	if err != nil {
   139  		panic(err.Error())
   140  	}
   141  	k.groupPolicyByGroupIndex, err = orm.NewIndex(groupPolicyTable, GroupPolicyByGroupIndexPrefix, func(value interface{}) ([]interface{}, error) {
   142  		return []interface{}{value.(*group.GroupPolicyInfo).GroupId}, nil
   143  	}, group.GroupPolicyInfo{}.GroupId)
   144  	if err != nil {
   145  		panic(err.Error())
   146  	}
   147  	k.groupPolicyByAdminIndex, err = orm.NewIndex(groupPolicyTable, GroupPolicyByAdminIndexPrefix, func(value interface{}) ([]interface{}, error) {
   148  		admin := value.(*group.GroupPolicyInfo).Admin
   149  		addr, err := accKeeper.AddressCodec().StringToBytes(admin)
   150  		if err != nil {
   151  			return nil, err
   152  		}
   153  		return []interface{}{addr}, nil
   154  	}, []byte{})
   155  	if err != nil {
   156  		panic(err.Error())
   157  	}
   158  	k.groupPolicyTable = *groupPolicyTable
   159  
   160  	// Proposal Table
   161  	proposalTable, err := orm.NewAutoUInt64Table([2]byte{ProposalTablePrefix}, ProposalTableSeqPrefix, &group.Proposal{}, cdc)
   162  	if err != nil {
   163  		panic(err.Error())
   164  	}
   165  	k.proposalByGroupPolicyIndex, err = orm.NewIndex(proposalTable, ProposalByGroupPolicyIndexPrefix, func(value interface{}) ([]interface{}, error) {
   166  		account := value.(*group.Proposal).GroupPolicyAddress
   167  		addr, err := accKeeper.AddressCodec().StringToBytes(account)
   168  		if err != nil {
   169  			return nil, err
   170  		}
   171  		return []interface{}{addr}, nil
   172  	}, []byte{})
   173  	if err != nil {
   174  		panic(err.Error())
   175  	}
   176  	k.proposalsByVotingPeriodEnd, err = orm.NewIndex(proposalTable, ProposalsByVotingPeriodEndPrefix, func(value interface{}) ([]interface{}, error) {
   177  		votingPeriodEnd := value.(*group.Proposal).VotingPeriodEnd
   178  		return []interface{}{sdk.FormatTimeBytes(votingPeriodEnd)}, nil
   179  	}, []byte{})
   180  	if err != nil {
   181  		panic(err.Error())
   182  	}
   183  	k.proposalTable = *proposalTable
   184  
   185  	// Vote Table
   186  	voteTable, err := orm.NewPrimaryKeyTable([2]byte{VoteTablePrefix}, &group.Vote{}, cdc)
   187  	if err != nil {
   188  		panic(err.Error())
   189  	}
   190  	k.voteByProposalIndex, err = orm.NewIndex(voteTable, VoteByProposalIndexPrefix, func(value interface{}) ([]interface{}, error) {
   191  		return []interface{}{value.(*group.Vote).ProposalId}, nil
   192  	}, group.Vote{}.ProposalId)
   193  	if err != nil {
   194  		panic(err.Error())
   195  	}
   196  	k.voteByVoterIndex, err = orm.NewIndex(voteTable, VoteByVoterIndexPrefix, func(value interface{}) ([]interface{}, error) {
   197  		addr, err := accKeeper.AddressCodec().StringToBytes(value.(*group.Vote).Voter)
   198  		if err != nil {
   199  			return nil, err
   200  		}
   201  		return []interface{}{addr}, nil
   202  	}, []byte{})
   203  	if err != nil {
   204  		panic(err.Error())
   205  	}
   206  	k.voteTable = *voteTable
   207  
   208  	if config.MaxMetadataLen == 0 {
   209  		config.MaxMetadataLen = group.DefaultConfig().MaxMetadataLen
   210  	}
   211  	if config.MaxExecutionPeriod == 0 {
   212  		config.MaxExecutionPeriod = group.DefaultConfig().MaxExecutionPeriod
   213  	}
   214  	k.config = config
   215  
   216  	return k
   217  }
   218  
   219  // Logger returns a module-specific logger.
   220  func (k Keeper) Logger(ctx sdk.Context) log.Logger {
   221  	return ctx.Logger().With("module", fmt.Sprintf("x/%s", group.ModuleName))
   222  }
   223  
   224  // GetGroupSequence returns the current value of the group table sequence
   225  func (k Keeper) GetGroupSequence(ctx sdk.Context) uint64 {
   226  	return k.groupTable.Sequence().CurVal(ctx.KVStore(k.key))
   227  }
   228  
   229  // GetGroupPolicySeq returns the current value of the group policy table sequence
   230  func (k Keeper) GetGroupPolicySeq(ctx sdk.Context) uint64 {
   231  	return k.groupPolicySeq.CurVal(ctx.KVStore(k.key))
   232  }
   233  
   234  // proposalsByVPEnd returns all proposals whose voting_period_end is after the `endTime` time argument.
   235  func (k Keeper) proposalsByVPEnd(ctx sdk.Context, endTime time.Time) (proposals []group.Proposal, err error) {
   236  	timeBytes := sdk.FormatTimeBytes(endTime)
   237  	it, err := k.proposalsByVotingPeriodEnd.PrefixScan(ctx.KVStore(k.key), nil, timeBytes)
   238  	if err != nil {
   239  		return proposals, err
   240  	}
   241  	defer it.Close()
   242  
   243  	for {
   244  		// Important: this following line cannot be outside of the for loop.
   245  		// It seems that when one unmarshals into the same `group.Proposal`
   246  		// reference, then gogoproto somehow "adds" the new bytes to the old
   247  		// object for some fields. When running simulations, for proposals with
   248  		// each 1-2 proposers, after a couple of loop iterations we got to a
   249  		// proposal with 60k+ proposers.
   250  		// So we're declaring a local variable that gets GCed.
   251  		//
   252  		// Also see `x/group/types/proposal_test.go`, TestGogoUnmarshalProposal().
   253  		var proposal group.Proposal
   254  		_, err := it.LoadNext(&proposal)
   255  		if errors.ErrORMIteratorDone.Is(err) {
   256  			break
   257  		}
   258  		if err != nil {
   259  			return proposals, err
   260  		}
   261  		proposals = append(proposals, proposal)
   262  	}
   263  
   264  	return proposals, nil
   265  }
   266  
   267  // pruneProposal deletes a proposal from state.
   268  func (k Keeper) pruneProposal(ctx sdk.Context, proposalID uint64) error {
   269  	store := ctx.KVStore(k.key)
   270  
   271  	err := k.proposalTable.Delete(store, proposalID)
   272  	if err != nil {
   273  		return err
   274  	}
   275  
   276  	k.Logger(ctx).Debug(fmt.Sprintf("Pruned proposal %d", proposalID))
   277  	return nil
   278  }
   279  
   280  // abortProposals iterates through all proposals by group policy index
   281  // and marks submitted proposals as aborted.
   282  func (k Keeper) abortProposals(ctx sdk.Context, groupPolicyAddr sdk.AccAddress) error {
   283  	proposals, err := k.proposalsByGroupPolicy(ctx, groupPolicyAddr)
   284  	if err != nil {
   285  		return err
   286  	}
   287  
   288  	//nolint:gosec // "implicit memory aliasing in the for loop (because of the pointer on &proposalInfo)"
   289  	for _, proposalInfo := range proposals {
   290  		// Mark all proposals still in the voting phase as aborted.
   291  		if proposalInfo.Status == group.PROPOSAL_STATUS_SUBMITTED {
   292  			proposalInfo.Status = group.PROPOSAL_STATUS_ABORTED
   293  
   294  			if err := k.proposalTable.Update(ctx.KVStore(k.key), proposalInfo.Id, &proposalInfo); err != nil {
   295  				return err
   296  			}
   297  		}
   298  	}
   299  	return nil
   300  }
   301  
   302  // proposalsByGroupPolicy returns all proposals for a given group policy.
   303  func (k Keeper) proposalsByGroupPolicy(ctx sdk.Context, groupPolicyAddr sdk.AccAddress) ([]group.Proposal, error) {
   304  	proposalIt, err := k.proposalByGroupPolicyIndex.Get(ctx.KVStore(k.key), groupPolicyAddr.Bytes())
   305  	if err != nil {
   306  		return nil, err
   307  	}
   308  	defer proposalIt.Close()
   309  
   310  	var proposals []group.Proposal
   311  	for {
   312  		var proposalInfo group.Proposal
   313  		_, err = proposalIt.LoadNext(&proposalInfo)
   314  		if errors.ErrORMIteratorDone.Is(err) {
   315  			break
   316  		}
   317  		if err != nil {
   318  			return proposals, err
   319  		}
   320  
   321  		proposals = append(proposals, proposalInfo)
   322  	}
   323  	return proposals, nil
   324  }
   325  
   326  // pruneVotes prunes all votes for a proposal from state.
   327  func (k Keeper) pruneVotes(ctx sdk.Context, proposalID uint64) error {
   328  	votes, err := k.votesByProposal(ctx, proposalID)
   329  	if err != nil {
   330  		return err
   331  	}
   332  
   333  	//nolint:gosec // "implicit memory aliasing in the for loop (because of the pointer on &v)"
   334  	for _, v := range votes {
   335  		err = k.voteTable.Delete(ctx.KVStore(k.key), &v)
   336  		if err != nil {
   337  			return err
   338  		}
   339  	}
   340  
   341  	return nil
   342  }
   343  
   344  // votesByProposal returns all votes for a given proposal.
   345  func (k Keeper) votesByProposal(ctx sdk.Context, proposalID uint64) ([]group.Vote, error) {
   346  	it, err := k.voteByProposalIndex.Get(ctx.KVStore(k.key), proposalID)
   347  	if err != nil {
   348  		return nil, err
   349  	}
   350  	defer it.Close()
   351  
   352  	var votes []group.Vote
   353  	for {
   354  		var vote group.Vote
   355  		_, err = it.LoadNext(&vote)
   356  		if errors.ErrORMIteratorDone.Is(err) {
   357  			break
   358  		}
   359  		if err != nil {
   360  			return votes, err
   361  		}
   362  		votes = append(votes, vote)
   363  	}
   364  	return votes, nil
   365  }
   366  
   367  // PruneProposals prunes all proposals that are expired, i.e. whose
   368  // `voting_period + max_execution_period` is greater than the current block
   369  // time.
   370  func (k Keeper) PruneProposals(ctx sdk.Context) error {
   371  	proposals, err := k.proposalsByVPEnd(ctx, ctx.BlockTime().Add(-k.config.MaxExecutionPeriod))
   372  	if err != nil {
   373  		return nil
   374  	}
   375  	for _, proposal := range proposals {
   376  		err := k.pruneProposal(ctx, proposal.Id)
   377  		if err != nil {
   378  			return err
   379  		}
   380  		// Emit event for proposal finalized with its result
   381  		if err := ctx.EventManager().EmitTypedEvent(
   382  			&group.EventProposalPruned{
   383  				ProposalId:  proposal.Id,
   384  				Status:      proposal.Status,
   385  				TallyResult: &proposal.FinalTallyResult,
   386  			}); err != nil {
   387  			return err
   388  		}
   389  	}
   390  
   391  	return nil
   392  }
   393  
   394  // TallyProposalsAtVPEnd iterates over all proposals whose voting period
   395  // has ended, tallies their votes, prunes them, and updates the proposal's
   396  // `FinalTallyResult` field.
   397  func (k Keeper) TallyProposalsAtVPEnd(ctx sdk.Context) error {
   398  	proposals, err := k.proposalsByVPEnd(ctx, ctx.BlockTime())
   399  	if err != nil {
   400  		return nil
   401  	}
   402  	//nolint:gosec // "implicit memory aliasing in the for loop (because of the pointers in the loop)"
   403  	for _, proposal := range proposals {
   404  		policyInfo, err := k.getGroupPolicyInfo(ctx, proposal.GroupPolicyAddress)
   405  		if err != nil {
   406  			return errorsmod.Wrap(err, "group policy")
   407  		}
   408  
   409  		electorate, err := k.getGroupInfo(ctx, policyInfo.GroupId)
   410  		if err != nil {
   411  			return errorsmod.Wrap(err, "group")
   412  		}
   413  
   414  		proposalID := proposal.Id
   415  		if proposal.Status == group.PROPOSAL_STATUS_ABORTED || proposal.Status == group.PROPOSAL_STATUS_WITHDRAWN {
   416  			if err := k.pruneProposal(ctx, proposalID); err != nil {
   417  				return err
   418  			}
   419  			if err := k.pruneVotes(ctx, proposalID); err != nil {
   420  				return err
   421  			}
   422  			// Emit event for proposal finalized with its result
   423  			if err := ctx.EventManager().EmitTypedEvent(
   424  				&group.EventProposalPruned{
   425  					ProposalId: proposal.Id,
   426  					Status:     proposal.Status,
   427  				}); err != nil {
   428  				return err
   429  			}
   430  		} else if proposal.Status == group.PROPOSAL_STATUS_SUBMITTED {
   431  			if err := k.doTallyAndUpdate(ctx, &proposal, electorate, policyInfo); err != nil {
   432  				return errorsmod.Wrap(err, "doTallyAndUpdate")
   433  			}
   434  
   435  			if err := k.proposalTable.Update(ctx.KVStore(k.key), proposal.Id, &proposal); err != nil {
   436  				return errorsmod.Wrap(err, "proposal update")
   437  			}
   438  		}
   439  		// Note: We do nothing if the proposal has been marked as ACCEPTED or
   440  		// REJECTED.
   441  	}
   442  	return nil
   443  }