github.com/Finschia/finschia-sdk@v0.48.1/x/foundation/keeper/internal/proposal.go (about) 1 package internal 2 3 import ( 4 "time" 5 6 sdk "github.com/Finschia/finschia-sdk/types" 7 sdkerrors "github.com/Finschia/finschia-sdk/types/errors" 8 "github.com/Finschia/finschia-sdk/x/foundation" 9 ) 10 11 func (k Keeper) newProposalID(ctx sdk.Context) uint64 { 12 id := k.getPreviousProposalID(ctx) + 1 13 k.setPreviousProposalID(ctx, id) 14 15 return id 16 } 17 18 func (k Keeper) getPreviousProposalID(ctx sdk.Context) uint64 { 19 store := ctx.KVStore(k.storeKey) 20 bz := store.Get(previousProposalIDKey) 21 if len(bz) == 0 { 22 panic("previous proposal ID hasn't been set") 23 } 24 return Uint64FromBytes(bz) 25 } 26 27 func (k Keeper) setPreviousProposalID(ctx sdk.Context, id uint64) { 28 store := ctx.KVStore(k.storeKey) 29 store.Set(previousProposalIDKey, Uint64ToBytes(id)) 30 } 31 32 func (k Keeper) SubmitProposal(ctx sdk.Context, proposers []string, metadata string, msgs []sdk.Msg) (*uint64, error) { 33 if err := validateMetadata(metadata, k.config); err != nil { 34 return nil, err 35 } 36 37 foundationInfo := k.GetFoundationInfo(ctx) 38 authority := sdk.MustAccAddressFromBech32(k.GetAuthority()) 39 if err := ensureMsgAuthz(msgs, authority); err != nil { 40 return nil, err 41 } 42 43 // Prevent proposal that can not succeed. 44 policy := foundationInfo.GetDecisionPolicy() 45 if err := policy.Validate(foundationInfo, k.config); err != nil { 46 return nil, err 47 } 48 49 id := k.newProposalID(ctx) 50 proposal := foundation.Proposal{ 51 Id: id, 52 Metadata: metadata, 53 Proposers: proposers, 54 SubmitTime: ctx.BlockTime(), 55 FoundationVersion: foundationInfo.Version, 56 Status: foundation.PROPOSAL_STATUS_SUBMITTED, 57 ExecutorResult: foundation.PROPOSAL_EXECUTOR_RESULT_NOT_RUN, 58 VotingPeriodEnd: ctx.BlockTime().Add(policy.GetVotingPeriod()), 59 FinalTallyResult: foundation.DefaultTallyResult(), 60 } 61 if err := proposal.SetMsgs(msgs); err != nil { 62 return nil, err 63 } 64 65 k.setProposal(ctx, proposal) 66 k.addProposalToVPEndQueue(ctx, proposal) 67 68 return &id, nil 69 } 70 71 func (k Keeper) WithdrawProposal(ctx sdk.Context, proposalID uint64) error { 72 proposal, err := k.GetProposal(ctx, proposalID) 73 if err != nil { 74 return err 75 } 76 77 // Ensure the proposal can be withdrawn. 78 if proposal.Status != foundation.PROPOSAL_STATUS_SUBMITTED { 79 return sdkerrors.ErrInvalidRequest.Wrapf("cannot withdraw a proposal with the status of %s", proposal.Status) 80 } 81 82 proposal.Status = foundation.PROPOSAL_STATUS_WITHDRAWN 83 k.setProposal(ctx, *proposal) 84 85 return nil 86 } 87 88 // pruneProposal deletes a proposal from state. 89 func (k Keeper) pruneProposal(ctx sdk.Context, proposal foundation.Proposal) { 90 k.pruneVotes(ctx, proposal.Id) 91 k.removeProposalFromVPEndQueue(ctx, proposal) 92 k.deleteProposal(ctx, proposal.Id) 93 } 94 95 // PruneExpiredProposals prunes all proposals which are expired, 96 // i.e. whose `submit_time + voting_period + max_execution_period` is smaller than (or equal to) now. 97 func (k Keeper) PruneExpiredProposals(ctx sdk.Context) { 98 votingPeriodEnd := ctx.BlockTime().Add(-k.config.MaxExecutionPeriod).Add(time.Nanosecond) 99 100 var proposals []foundation.Proposal 101 k.iterateProposalsByVPEnd(ctx, votingPeriodEnd, func(proposal foundation.Proposal) (stop bool) { 102 proposals = append(proposals, proposal) 103 return false 104 }) 105 106 for _, proposal := range proposals { 107 k.pruneProposal(ctx, proposal) 108 } 109 } 110 111 // abortOldProposals aborts all proposals which have lower version than the current foundation's 112 func (k Keeper) abortOldProposals(ctx sdk.Context) { 113 latestVersion := k.GetFoundationInfo(ctx).Version 114 115 k.iterateProposals(ctx, func(proposal foundation.Proposal) (stop bool) { 116 if proposal.FoundationVersion == latestVersion { 117 return true 118 } 119 120 if proposal.Status == foundation.PROPOSAL_STATUS_SUBMITTED { 121 k.pruneVotes(ctx, proposal.Id) 122 123 proposal.Status = foundation.PROPOSAL_STATUS_ABORTED 124 k.setProposal(ctx, proposal) 125 } 126 127 return false 128 }) 129 } 130 131 func (k Keeper) GetProposals(ctx sdk.Context) []foundation.Proposal { 132 var proposals []foundation.Proposal 133 k.iterateProposals(ctx, func(proposal foundation.Proposal) (stop bool) { 134 proposals = append(proposals, proposal) 135 return false 136 }) 137 138 return proposals 139 } 140 141 func (k Keeper) iterateProposals(ctx sdk.Context, fn func(proposal foundation.Proposal) (stop bool)) { 142 store := ctx.KVStore(k.storeKey) 143 prefix := proposalKeyPrefix 144 iterator := sdk.KVStorePrefixIterator(store, prefix) 145 defer iterator.Close() 146 147 for ; iterator.Valid(); iterator.Next() { 148 var proposal foundation.Proposal 149 k.cdc.MustUnmarshal(iterator.Value(), &proposal) 150 if stop := fn(proposal); stop { 151 break 152 } 153 } 154 } 155 156 func (k Keeper) iterateProposalsByVPEnd(ctx sdk.Context, endTime time.Time, fn func(proposal foundation.Proposal) (stop bool)) { 157 store := ctx.KVStore(k.storeKey) 158 iter := store.Iterator(proposalByVPEndKeyPrefix, sdk.PrefixEndBytes(append(proposalByVPEndKeyPrefix, sdk.FormatTimeBytes(endTime)...))) 159 defer iter.Close() 160 161 for ; iter.Valid(); iter.Next() { 162 _, id := splitProposalByVPEndKey(iter.Key()) 163 164 proposal, err := k.GetProposal(ctx, id) 165 if err != nil { 166 panic(err) 167 } 168 169 if fn(*proposal) { 170 break 171 } 172 } 173 } 174 175 func (k Keeper) UpdateTallyOfVPEndProposals(ctx sdk.Context) { 176 var proposals []foundation.Proposal 177 k.iterateProposalsByVPEnd(ctx, ctx.BlockTime(), func(proposal foundation.Proposal) (stop bool) { 178 proposals = append(proposals, proposal) 179 return false 180 }) 181 182 for _, proposal := range proposals { 183 proposal := proposal 184 185 if proposal.Status == foundation.PROPOSAL_STATUS_ABORTED || proposal.Status == foundation.PROPOSAL_STATUS_WITHDRAWN { 186 k.pruneProposal(ctx, proposal) 187 continue 188 } 189 190 if err := k.doTallyAndUpdate(ctx, &proposal); err != nil { 191 panic(err) 192 } 193 k.setProposal(ctx, proposal) 194 } 195 } 196 197 func (k Keeper) GetProposal(ctx sdk.Context, id uint64) (*foundation.Proposal, error) { 198 store := ctx.KVStore(k.storeKey) 199 key := proposalKey(id) 200 bz := store.Get(key) 201 if len(bz) == 0 { 202 return nil, sdkerrors.ErrNotFound.Wrapf("No proposal for id: %d", id) 203 } 204 205 var proposal foundation.Proposal 206 k.cdc.MustUnmarshal(bz, &proposal) 207 208 return &proposal, nil 209 } 210 211 func (k Keeper) setProposal(ctx sdk.Context, proposal foundation.Proposal) { 212 store := ctx.KVStore(k.storeKey) 213 key := proposalKey(proposal.Id) 214 215 bz := k.cdc.MustMarshal(&proposal) 216 store.Set(key, bz) 217 } 218 219 func (k Keeper) deleteProposal(ctx sdk.Context, proposalID uint64) { 220 store := ctx.KVStore(k.storeKey) 221 key := proposalKey(proposalID) 222 store.Delete(key) 223 } 224 225 func (k Keeper) addProposalToVPEndQueue(ctx sdk.Context, proposal foundation.Proposal) { 226 store := ctx.KVStore(k.storeKey) 227 key := proposalByVPEndKey(proposal.VotingPeriodEnd, proposal.Id) 228 store.Set(key, []byte{}) 229 } 230 231 func (k Keeper) removeProposalFromVPEndQueue(ctx sdk.Context, proposal foundation.Proposal) { 232 store := ctx.KVStore(k.storeKey) 233 key := proposalByVPEndKey(proposal.VotingPeriodEnd, proposal.Id) 234 store.Delete(key) 235 } 236 237 func validateActorForProposal(address string, proposal foundation.Proposal) error { 238 for _, proposer := range proposal.Proposers { 239 if address == proposer { 240 return nil 241 } 242 } 243 244 return sdkerrors.ErrUnauthorized.Wrapf("not a proposer: %s", address) 245 }