github.com/cosmos/cosmos-sdk@v0.50.10/x/gov/abci.go (about) 1 package gov 2 3 import ( 4 "errors" 5 "fmt" 6 "time" 7 8 "cosmossdk.io/collections" 9 "cosmossdk.io/log" 10 11 "github.com/cosmos/cosmos-sdk/baseapp" 12 "github.com/cosmos/cosmos-sdk/telemetry" 13 sdk "github.com/cosmos/cosmos-sdk/types" 14 "github.com/cosmos/cosmos-sdk/x/gov/keeper" 15 "github.com/cosmos/cosmos-sdk/x/gov/types" 16 v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" 17 ) 18 19 // EndBlocker called every block, process inflation, update validator set. 20 func EndBlocker(ctx sdk.Context, keeper *keeper.Keeper) error { 21 defer telemetry.ModuleMeasureSince(types.ModuleName, telemetry.Now(), telemetry.MetricKeyEndBlocker) 22 23 logger := ctx.Logger().With("module", "x/"+types.ModuleName) 24 // delete dead proposals from store and returns theirs deposits. 25 // A proposal is dead when it's inactive and didn't get enough deposit on time to get into voting phase. 26 rng := collections.NewPrefixUntilPairRange[time.Time, uint64](ctx.BlockTime()) 27 err := keeper.InactiveProposalsQueue.Walk(ctx, rng, func(key collections.Pair[time.Time, uint64], _ uint64) (bool, error) { 28 proposal, err := keeper.Proposals.Get(ctx, key.K2()) 29 if err != nil { 30 // if the proposal has an encoding error, this means it cannot be processed by x/gov 31 // this could be due to some types missing their registration 32 // instead of returning an error (i.e, halting the chain), we fail the proposal 33 if errors.Is(err, collections.ErrEncoding) { 34 proposal.Id = key.K2() 35 if err := failUnsupportedProposal(logger, ctx, keeper, proposal, err.Error(), false); err != nil { 36 return false, err 37 } 38 39 if err = keeper.DeleteProposal(ctx, proposal.Id); err != nil { 40 return false, err 41 } 42 43 return false, nil 44 } 45 46 return false, err 47 } 48 49 if err = keeper.DeleteProposal(ctx, proposal.Id); err != nil { 50 return false, err 51 } 52 53 params, err := keeper.Params.Get(ctx) 54 if err != nil { 55 return false, err 56 } 57 if !params.BurnProposalDepositPrevote { 58 err = keeper.RefundAndDeleteDeposits(ctx, proposal.Id) // refund deposit if proposal got removed without getting 100% of the proposal 59 } else { 60 err = keeper.DeleteAndBurnDeposits(ctx, proposal.Id) // burn the deposit if proposal got removed without getting 100% of the proposal 61 } 62 63 if err != nil { 64 return false, err 65 } 66 67 // called when proposal become inactive 68 cacheCtx, writeCache := ctx.CacheContext() 69 err = keeper.Hooks().AfterProposalFailedMinDeposit(cacheCtx, proposal.Id) 70 if err == nil { // purposely ignoring the error here not to halt the chain if the hook fails 71 writeCache() 72 } else { 73 keeper.Logger(ctx).Error("failed to execute AfterProposalFailedMinDeposit hook", "error", err) 74 } 75 76 ctx.EventManager().EmitEvent( 77 sdk.NewEvent( 78 types.EventTypeInactiveProposal, 79 sdk.NewAttribute(types.AttributeKeyProposalID, fmt.Sprintf("%d", proposal.Id)), 80 sdk.NewAttribute(types.AttributeKeyProposalResult, types.AttributeValueProposalDropped), 81 ), 82 ) 83 84 logger.Info( 85 "proposal did not meet minimum deposit; deleted", 86 "proposal", proposal.Id, 87 "expedited", proposal.Expedited, 88 "title", proposal.Title, 89 "min_deposit", sdk.NewCoins(proposal.GetMinDepositFromParams(params)...).String(), 90 "total_deposit", sdk.NewCoins(proposal.TotalDeposit...).String(), 91 ) 92 93 return false, nil 94 }) 95 if err != nil { 96 return err 97 } 98 99 // fetch active proposals whose voting periods have ended (are passed the block time) 100 rng = collections.NewPrefixUntilPairRange[time.Time, uint64](ctx.BlockTime()) 101 err = keeper.ActiveProposalsQueue.Walk(ctx, rng, func(key collections.Pair[time.Time, uint64], _ uint64) (bool, error) { 102 proposal, err := keeper.Proposals.Get(ctx, key.K2()) 103 if err != nil { 104 // if the proposal has an encoding error, this means it cannot be processed by x/gov 105 // this could be due to some types missing their registration 106 // instead of returning an error (i.e, halting the chain), we fail the proposal 107 if errors.Is(err, collections.ErrEncoding) { 108 proposal.Id = key.K2() 109 if err := failUnsupportedProposal(logger, ctx, keeper, proposal, err.Error(), true); err != nil { 110 return false, err 111 } 112 113 if err = keeper.ActiveProposalsQueue.Remove(ctx, collections.Join(*proposal.VotingEndTime, proposal.Id)); err != nil { 114 return false, err 115 } 116 117 return false, nil 118 } 119 120 return false, err 121 } 122 123 var tagValue, logMsg string 124 125 passes, burnDeposits, tallyResults, err := keeper.Tally(ctx, proposal) 126 if err != nil { 127 return false, err 128 } 129 130 // If an expedited proposal fails, we do not want to update 131 // the deposit at this point since the proposal is converted to regular. 132 // As a result, the deposits are either deleted or refunded in all cases 133 // EXCEPT when an expedited proposal fails. 134 if !(proposal.Expedited && !passes) { 135 if burnDeposits { 136 err = keeper.DeleteAndBurnDeposits(ctx, proposal.Id) 137 } else { 138 err = keeper.RefundAndDeleteDeposits(ctx, proposal.Id) 139 } 140 if err != nil { 141 return false, err 142 } 143 } 144 145 if err = keeper.ActiveProposalsQueue.Remove(ctx, collections.Join(*proposal.VotingEndTime, proposal.Id)); err != nil { 146 return false, err 147 } 148 149 switch { 150 case passes: 151 var ( 152 idx int 153 events sdk.Events 154 msg sdk.Msg 155 ) 156 157 // attempt to execute all messages within the passed proposal 158 // Messages may mutate state thus we use a cached context. If one of 159 // the handlers fails, no state mutation is written and the error 160 // message is logged. 161 cacheCtx, writeCache := ctx.CacheContext() 162 messages, err := proposal.GetMsgs() 163 if err != nil { 164 proposal.Status = v1.StatusFailed 165 proposal.FailedReason = err.Error() 166 tagValue = types.AttributeValueProposalFailed 167 logMsg = fmt.Sprintf("passed proposal (%v) failed to execute; msgs: %s", proposal, err) 168 169 break 170 } 171 172 // execute all messages 173 for idx, msg = range messages { 174 handler := keeper.Router().Handler(msg) 175 var res *sdk.Result 176 res, err = safeExecuteHandler(cacheCtx, msg, handler) 177 if err != nil { 178 break 179 } 180 181 events = append(events, res.GetEvents()...) 182 } 183 184 // `err == nil` when all handlers passed. 185 // Or else, `idx` and `err` are populated with the msg index and error. 186 if err == nil { 187 proposal.Status = v1.StatusPassed 188 tagValue = types.AttributeValueProposalPassed 189 logMsg = "passed" 190 191 // write state to the underlying multi-store 192 writeCache() 193 194 // propagate the msg events to the current context 195 ctx.EventManager().EmitEvents(events) 196 } else { 197 proposal.Status = v1.StatusFailed 198 proposal.FailedReason = err.Error() 199 tagValue = types.AttributeValueProposalFailed 200 logMsg = fmt.Sprintf("passed, but msg %d (%s) failed on execution: %s", idx, sdk.MsgTypeURL(msg), err) 201 } 202 case proposal.Expedited: 203 // When expedited proposal fails, it is converted 204 // to a regular proposal. As a result, the voting period is extended, and, 205 // once the regular voting period expires again, the tally is repeated 206 // according to the regular proposal rules. 207 proposal.Expedited = false 208 params, err := keeper.Params.Get(ctx) 209 if err != nil { 210 return false, err 211 } 212 endTime := proposal.VotingStartTime.Add(*params.VotingPeriod) 213 proposal.VotingEndTime = &endTime 214 215 err = keeper.ActiveProposalsQueue.Set(ctx, collections.Join(*proposal.VotingEndTime, proposal.Id), proposal.Id) 216 if err != nil { 217 return false, err 218 } 219 220 tagValue = types.AttributeValueExpeditedProposalRejected 221 logMsg = "expedited proposal converted to regular" 222 default: 223 proposal.Status = v1.StatusRejected 224 proposal.FailedReason = "proposal did not get enough votes to pass" 225 tagValue = types.AttributeValueProposalRejected 226 logMsg = "rejected" 227 } 228 229 proposal.FinalTallyResult = &tallyResults 230 231 err = keeper.SetProposal(ctx, proposal) 232 if err != nil { 233 return false, err 234 } 235 236 // when proposal become active 237 cacheCtx, writeCache := ctx.CacheContext() 238 err = keeper.Hooks().AfterProposalVotingPeriodEnded(cacheCtx, proposal.Id) 239 if err == nil { // purposely ignoring the error here not to halt the chain if the hook fails 240 writeCache() 241 } else { 242 keeper.Logger(ctx).Error("failed to execute AfterProposalVotingPeriodEnded hook", "error", err) 243 } 244 245 logger.Info( 246 "proposal tallied", 247 "proposal", proposal.Id, 248 "status", proposal.Status.String(), 249 "expedited", proposal.Expedited, 250 "title", proposal.Title, 251 "results", logMsg, 252 ) 253 254 ctx.EventManager().EmitEvent( 255 sdk.NewEvent( 256 types.EventTypeActiveProposal, 257 sdk.NewAttribute(types.AttributeKeyProposalID, fmt.Sprintf("%d", proposal.Id)), 258 sdk.NewAttribute(types.AttributeKeyProposalResult, tagValue), 259 sdk.NewAttribute(types.AttributeKeyProposalLog, logMsg), 260 ), 261 ) 262 263 return false, nil 264 }) 265 if err != nil { 266 return err 267 } 268 return nil 269 } 270 271 // executes handle(msg) and recovers from panic. 272 func safeExecuteHandler(ctx sdk.Context, msg sdk.Msg, handler baseapp.MsgServiceHandler, 273 ) (res *sdk.Result, err error) { 274 defer func() { 275 if r := recover(); r != nil { 276 err = fmt.Errorf("handling x/gov proposal msg [%s] PANICKED: %v", msg, r) 277 } 278 }() 279 res, err = handler(ctx, msg) 280 return 281 } 282 283 // failUnsupportedProposal fails a proposal that cannot be processed by gov 284 func failUnsupportedProposal( 285 logger log.Logger, 286 ctx sdk.Context, 287 keeper *keeper.Keeper, 288 proposal v1.Proposal, 289 errMsg string, 290 active bool, 291 ) error { 292 proposal.Status = v1.StatusFailed 293 proposal.FailedReason = fmt.Sprintf("proposal failed because it cannot be processed by gov: %s", errMsg) 294 proposal.Messages = nil // clear out the messages 295 296 if err := keeper.SetProposal(ctx, proposal); err != nil { 297 return err 298 } 299 300 if err := keeper.RefundAndDeleteDeposits(ctx, proposal.Id); err != nil { 301 return err 302 } 303 304 eventType := types.EventTypeInactiveProposal 305 if active { 306 eventType = types.EventTypeActiveProposal 307 } 308 309 ctx.EventManager().EmitEvent( 310 sdk.NewEvent( 311 eventType, 312 sdk.NewAttribute(types.AttributeKeyProposalID, fmt.Sprintf("%d", proposal.Id)), 313 sdk.NewAttribute(types.AttributeKeyProposalResult, types.AttributeValueProposalFailed), 314 ), 315 ) 316 317 logger.Info( 318 "proposal failed to decode; deleted", 319 "proposal", proposal.Id, 320 "expedited", proposal.Expedited, 321 "title", proposal.Title, 322 "results", errMsg, 323 ) 324 325 return nil 326 }