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 }