-
Notifications
You must be signed in to change notification settings - Fork 16
/
Copy pathabci.go
232 lines (198 loc) · 8.49 KB
/
abci.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
package gov
import (
"fmt"
"time"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/atomone-hub/atomone/x/gov/keeper"
"github.com/atomone-hub/atomone/x/gov/types"
v1 "github.com/atomone-hub/atomone/x/gov/types/v1"
)
// EndBlocker called every block, process inflation, update validator set.
func EndBlocker(ctx sdk.Context, keeper *keeper.Keeper) {
defer telemetry.ModuleMeasureSince(types.ModuleName, time.Now(), telemetry.MetricKeyEndBlocker)
logger := keeper.Logger(ctx)
// delete dead proposals from store and returns theirs deposits.
// A proposal is dead when it's inactive and didn't get enough deposit on time to get into voting phase.
keeper.IterateInactiveProposalsQueue(ctx, ctx.BlockHeader().Time, func(proposal v1.Proposal) bool {
// before deleting, check one last time if the proposal has enough deposits to get into voting phase,
// maybe because min deposit decreased in the meantime.
minDeposit := keeper.GetMinDeposit(ctx)
if proposal.Status == v1.StatusDepositPeriod && sdk.NewCoins(proposal.TotalDeposit...).IsAllGTE(minDeposit) {
keeper.ActivateVotingPeriod(ctx, proposal)
return false
}
keeper.DeleteProposal(ctx, proposal.Id)
params := keeper.GetParams(ctx)
if !params.BurnProposalDepositPrevote {
keeper.RefundAndDeleteDeposits(ctx, proposal.Id) // refund deposit if proposal got removed without getting 100% of the proposal
} else {
keeper.DeleteAndBurnDeposits(ctx, proposal.Id) // burn the deposit if proposal got removed without getting 100% of the proposal
}
keeper.DecrementInactiveProposalsNumber(ctx)
// called when proposal become inactive
keeper.Hooks().AfterProposalFailedMinDeposit(ctx, proposal.Id)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeInactiveProposal,
sdk.NewAttribute(types.AttributeKeyProposalID, fmt.Sprintf("%d", proposal.Id)),
sdk.NewAttribute(types.AttributeKeyProposalResult, types.AttributeValueProposalDropped),
),
)
logger.Info(
"proposal did not meet minimum deposit; deleted",
"proposal", proposal.Id,
"min_deposit", sdk.NewCoins(minDeposit...).String(),
"total_deposit", sdk.NewCoins(proposal.TotalDeposit...).String(),
)
return false
})
// fetch proposals that are due to be checked for quorum
keeper.IterateQuorumCheckQueue(ctx, ctx.BlockTime(),
func(proposal v1.Proposal, endTime time.Time, quorumCheckEntry v1.QuorumCheckQueueEntry) bool {
params := keeper.GetParams(ctx)
// remove from queue
keeper.RemoveFromQuorumCheckQueue(ctx, proposal.Id, endTime)
// check if proposal passed quorum
quorum := keeper.HasReachedQuorum(ctx, proposal)
logMsg := "proposal did not pass quorum after timeout, but was removed from quorum check queue"
tagValue := types.AttributeValueProposalQuorumNotMet
if quorum {
logMsg = "proposal passed quorum before timeout, vote period was not extended"
tagValue = types.AttributeValueProposalQuorumMet
if quorumCheckEntry.QuorumChecksDone > 0 {
// proposal passed quorum after timeout, extend voting period.
// canonically, we consider the first quorum check to be "right after" the quorum timeout has elapsed,
// so if quorum is reached at the first check, we don't extend the voting period.
endTime := ctx.BlockTime().Add(*params.MaxVotingPeriodExtension)
logMsg = fmt.Sprintf("proposal passed quorum after timeout, but vote end %s is already after %s", proposal.VotingEndTime, endTime)
if endTime.After(*proposal.VotingEndTime) {
logMsg = fmt.Sprintf("proposal passed quorum after timeout, vote end was extended from %s to %s", proposal.VotingEndTime, endTime)
// Update ActiveProposalsQueue with new VotingEndTime
keeper.RemoveFromActiveProposalQueue(ctx, proposal.Id, *proposal.VotingEndTime)
proposal.VotingEndTime = &endTime
keeper.InsertActiveProposalQueue(ctx, proposal.Id, *proposal.VotingEndTime)
keeper.SetProposal(ctx, proposal)
}
}
} else if quorumCheckEntry.QuorumChecksDone < quorumCheckEntry.QuorumCheckCount && proposal.VotingEndTime.After(ctx.BlockTime()) {
// proposal did not pass quorum and is still active, add back to queue with updated time key and counter.
// compute time interval between quorum checks
quorumCheckPeriod := proposal.VotingEndTime.Sub(*quorumCheckEntry.QuorumTimeoutTime)
t := quorumCheckPeriod / time.Duration(quorumCheckEntry.QuorumCheckCount)
// find time for next quorum check
nextQuorumCheckTime := endTime.Add(t)
if !nextQuorumCheckTime.After(ctx.BlockTime()) {
// next quorum check time is in the past, so add enough time intervals to get to the next quorum check time in the future.
d := ctx.BlockTime().Sub(nextQuorumCheckTime)
n := d / t
nextQuorumCheckTime = nextQuorumCheckTime.Add(t * (n + 1))
}
if nextQuorumCheckTime.After(*proposal.VotingEndTime) {
// next quorum check time is after the voting period ends, so adjust it to be equal to the voting period end time
nextQuorumCheckTime = *proposal.VotingEndTime
}
quorumCheckEntry.QuorumChecksDone++
keeper.InsertQuorumCheckQueue(ctx, proposal.Id, nextQuorumCheckTime, quorumCheckEntry)
logMsg = fmt.Sprintf("proposal did not pass quorum after timeout, next check happening at %s", nextQuorumCheckTime)
}
logger.Info(
"proposal quorum check",
"proposal", proposal.Id,
"title", proposal.Title,
"results", logMsg,
)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeQuorumCheck,
sdk.NewAttribute(types.AttributeKeyProposalID, fmt.Sprintf("%d", proposal.Id)),
sdk.NewAttribute(types.AttributeKeyProposalResult, tagValue),
),
)
return false
})
// fetch active proposals whose voting periods have ended (are passed the block time)
keeper.IterateActiveProposalsQueue(ctx, ctx.BlockHeader().Time, func(proposal v1.Proposal) bool {
var tagValue, logMsg string
passes, burnDeposits, tallyResults := keeper.Tally(ctx, proposal)
if burnDeposits {
keeper.DeleteAndBurnDeposits(ctx, proposal.Id)
} else {
keeper.RefundAndDeleteDeposits(ctx, proposal.Id)
}
if passes {
var (
idx int
events sdk.Events
msg sdk.Msg
)
// attempt to execute all messages within the passed proposal
// Messages may mutate state thus we use a cached context. If one of
// the handlers fails, no state mutation is written and the error
// message is logged.
cacheCtx, writeCache := ctx.CacheContext()
messages, err := proposal.GetMsgs()
if err == nil {
for idx, msg = range messages {
handler := keeper.Router().Handler(msg)
var res *sdk.Result
res, err = safeExecuteHandler(cacheCtx, msg, handler)
if err != nil {
break
}
events = append(events, res.GetEvents()...)
}
}
// `err == nil` when all handlers passed.
// Or else, `idx` and `err` are populated with the msg index and error.
if err == nil {
proposal.Status = v1.StatusPassed
tagValue = types.AttributeValueProposalPassed
logMsg = "passed"
// write state to the underlying multi-store
writeCache()
// propagate the msg events to the current context
ctx.EventManager().EmitEvents(events)
} else {
proposal.Status = v1.StatusFailed
tagValue = types.AttributeValueProposalFailed
logMsg = fmt.Sprintf("passed, but msg %d (%s) failed on execution: %s", idx, sdk.MsgTypeURL(msg), err)
}
} else {
proposal.Status = v1.StatusRejected
tagValue = types.AttributeValueProposalRejected
logMsg = "rejected"
}
proposal.FinalTallyResult = &tallyResults
keeper.SetProposal(ctx, proposal)
keeper.RemoveFromActiveProposalQueue(ctx, proposal.Id, *proposal.VotingEndTime)
keeper.DecrementActiveProposalsNumber(ctx)
// when proposal become active
keeper.Hooks().AfterProposalVotingPeriodEnded(ctx, proposal.Id)
logger.Info(
"proposal tallied",
"proposal", proposal.Id,
"results", logMsg,
)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeActiveProposal,
sdk.NewAttribute(types.AttributeKeyProposalID, fmt.Sprintf("%d", proposal.Id)),
sdk.NewAttribute(types.AttributeKeyProposalResult, tagValue),
),
)
return false
})
}
// executes handle(msg) and recovers from panic.
func safeExecuteHandler(ctx sdk.Context, msg sdk.Msg, handler baseapp.MsgServiceHandler,
) (res *sdk.Result, err error) {
defer func() {
if r := recover(); r != nil {
err = fmt.Errorf("handling x/gov proposal msg [%s] PANICKED: %v", msg, r)
}
}()
res, err = handler(ctx, msg)
return
}