-
Notifications
You must be signed in to change notification settings - Fork 40
/
abci.go
269 lines (225 loc) · 9.61 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
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
package keeper
import (
"fmt"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
abcitypes "github.com/tendermint/tendermint/abci/types"
hubtypes "github.com/sentinel-official/hub/types"
hubutils "github.com/sentinel-official/hub/utils"
"github.com/sentinel-official/hub/x/subscription/types"
)
// BeginBlock is a function that gets called at the beginning of every block.
// It processes the payouts scheduled to be made and performs the necessary actions accordingly.
func (k *Keeper) BeginBlock(ctx sdk.Context) {
// Iterate over all payouts that are scheduled to happen at the current block time.
k.IteratePayoutsForNextAt(ctx, ctx.BlockTime(), func(_ int, item types.Payout) (stop bool) {
k.Logger(ctx).Info("Found a scheduled payout", "id", item.ID)
// Delete the payout from the NextAt index before updating the NextAt value.
k.DeletePayoutForNextAt(ctx, item.NextAt, item.ID)
// Calculate the staking reward for the payout.
var (
accAddr = item.GetAddress()
nodeAddr = item.GetNodeAddress()
stakingShare = k.node.StakingShare(ctx)
stakingReward = hubutils.GetProportionOfCoin(item.Price, stakingShare)
)
// Move the staking reward from the deposit to the fee collector module account.
if err := k.SendCoinFromDepositToModule(ctx, accAddr, k.feeCollectorName, stakingReward); err != nil {
panic(err)
}
// Calculate the payment amount to be sent to the node address by subtracting the staking reward from the payout price.
payment := item.Price.Sub(stakingReward)
// Send the payment amount from the deposit to the node address.
if err := k.SendCoinFromDepositToAccount(ctx, accAddr, nodeAddr.Bytes(), payment); err != nil {
panic(err)
}
// Emit an event for the payout payment.
ctx.EventManager().EmitTypedEvent(
&types.EventPayForPayout{
Address: item.Address,
NodeAddress: item.NodeAddress,
Payment: payment.String(),
StakingReward: stakingReward.String(),
ID: item.ID,
},
)
// Decrement the remaining payout duration (in hours) by 1 and update the NextAt value.
item.Hours = item.Hours - 1
item.NextAt = item.NextAt.Add(time.Hour)
// If the payout duration has reached 0, set the NextAt value to an empty time.
if item.Hours == 0 {
item.NextAt = time.Time{}
}
// Update the payout in the store with the updated duration and NextAt value.
k.SetPayout(ctx, item)
// If the payout still has remaining duration (hours), update the NextAt index.
if item.Hours > 0 {
k.SetPayoutForNextAt(ctx, item.NextAt, item.ID)
}
return false
})
}
// EndBlock is a function that gets called at the end of every block.
// It processes the subscriptions that have become inactive and performs the necessary actions accordingly.
func (k *Keeper) EndBlock(ctx sdk.Context) []abcitypes.ValidatorUpdate {
// Get the status change delay from the store.
statusChangeDelay := k.StatusChangeDelay(ctx)
// Iterate over all subscriptions that have become inactive at the current block time.
k.IterateSubscriptionsForInactiveAt(ctx, ctx.BlockTime(), func(_ int, item types.Subscription) bool {
k.Logger(ctx).Info("Found an inactive subscription", "id", item.GetID(), "status", item.GetStatus())
// Delete the subscription from the InactiveAt index before updating the InactiveAt value.
k.DeleteSubscriptionForInactiveAt(ctx, item.GetInactiveAt(), item.GetID())
// If the subscription status is 'Active', update its InactiveAt value and set it to 'InactivePending'.
if item.GetStatus().Equal(hubtypes.StatusActive) {
// Run the SubscriptionInactivePendingHook to perform custom actions before setting the subscription to inactive pending state.
if err := k.SubscriptionInactivePendingHook(ctx, item.GetID()); err != nil {
panic(err)
}
item.SetInactiveAt(ctx.BlockTime().Add(statusChangeDelay))
item.SetStatus(hubtypes.StatusInactivePending)
item.SetStatusAt(ctx.BlockTime())
// Save the updated subscription to the store and update the InactiveAt index.
k.SetSubscription(ctx, item)
k.SetSubscriptionForInactiveAt(ctx, item.GetInactiveAt(), item.GetID())
// Emit an event to notify that the subscription status has been updated.
ctx.EventManager().EmitTypedEvent(
&types.EventUpdateStatus{
Status: hubtypes.StatusInactivePending,
Address: item.GetAddress().String(),
ID: item.GetID(),
PlanID: 0,
},
)
// If the subscription is a NodeSubscription and the duration is specified in hours (non-zero), update the associated payout.
if s, ok := item.(*types.NodeSubscription); ok && s.Hours != 0 {
payout, found := k.GetPayout(ctx, s.GetID())
if !found {
panic(fmt.Errorf("payout for subscription %d does not exist", s.GetID()))
}
var (
accAddr = payout.GetAddress()
nodeAddr = payout.GetNodeAddress()
)
// Delete the payout from the Store for the given account and node.
k.DeletePayoutForAccountByNode(ctx, accAddr, nodeAddr, payout.ID)
k.DeletePayoutForNextAt(ctx, payout.NextAt, payout.ID)
// Reset the `NextAt` field of the payout and update it in the Store.
payout.NextAt = time.Time{}
k.SetPayout(ctx, payout)
}
return false
}
// If the subscription status is not 'Active', handle the different types of subscriptions based on their attributes.
if s, ok := item.(*types.NodeSubscription); ok {
// Check if it has a non-zero bandwidth (Gigabytes != 0).
if s.Gigabytes != 0 {
// Calculate the gigabyte price based on the deposit amount and gigabytes.
var (
accAddr = item.GetAddress()
gigabytePrice = sdk.NewCoin(
s.Deposit.Denom,
s.Deposit.Amount.QuoRaw(s.Gigabytes),
)
)
// Get the allocation associated with the subscription and account.
alloc, found := k.GetAllocation(ctx, item.GetID(), accAddr)
if !found {
panic(fmt.Errorf("subscription allocation %d/%s does not exist", item.GetID(), accAddr))
}
// Calculate the amount paid based on the gigabyte price and utilized bandwidth.
var (
paidAmount = hubutils.AmountForBytes(gigabytePrice.Amount, alloc.UtilisedBytes)
refund = sdk.NewCoin(
s.Deposit.Denom,
s.Deposit.Amount.Sub(paidAmount),
)
)
// Refund the difference between the deposit and the amount paid to the node's account.
if err := k.SubtractDeposit(ctx, accAddr, refund); err != nil {
panic(err)
}
// Emit an event for the refund.
ctx.EventManager().EmitTypedEvent(
&types.EventRefund{
Address: s.Address,
Amount: refund.String(),
ID: s.ID,
},
)
}
// Check if the number of hours for the subscription is not zero.
if s.Hours != 0 {
// Get the payout information associated with the subscription ID.
payout, found := k.GetPayout(ctx, item.GetID())
if !found {
panic(fmt.Errorf("payout for subscription %d does not exist", item.GetID()))
}
// Calculate the refund amount by multiplying the payout price with the number of remaining hours.
var (
accAddr = payout.GetAddress()
refund = sdk.NewCoin(
payout.Price.Denom,
payout.Price.Amount.MulRaw(payout.Hours),
)
)
// Subtract the refund amount from the account's deposit balance.
if err := k.SubtractDeposit(ctx, accAddr, refund); err != nil {
panic(err)
}
// Emit an event for the refund.
ctx.EventManager().EmitTypedEvent(
&types.EventRefund{
Address: s.Address,
Amount: refund.String(),
ID: s.ID,
},
)
}
}
// Iterate over all allocations associated with the subscription and delete them from the store.
k.IterateAllocationsForSubscription(ctx, item.GetID(), func(_ int, alloc types.Allocation) bool {
accAddr := alloc.GetAddress()
k.DeleteAllocation(ctx, item.GetID(), accAddr)
k.DeleteSubscriptionForAccount(ctx, accAddr, item.GetID())
return false
})
// Based on the subscription type, perform additional cleanup actions.
switch s := item.(type) {
case *types.NodeSubscription:
// For node-level subscriptions, delete the subscription from the NodeAddress index.
k.DeleteSubscriptionForNode(ctx, s.GetNodeAddress(), s.GetID())
case *types.PlanSubscription:
// For plan-level subscriptions, delete the subscription from the PlanID index.
k.DeleteSubscriptionForPlan(ctx, s.PlanID, s.GetID())
default:
// If the subscription type is not recognized, panic with an error indicating an invalid subscription type.
panic(fmt.Errorf("invalid subscription %d with type %T", item.GetID(), item))
}
// Finally, delete the subscription from the store and emit an event to notify its status change to 'Inactive'.
k.DeleteSubscription(ctx, item.GetID())
ctx.EventManager().EmitTypedEvent(
&types.EventUpdateStatus{
Status: hubtypes.StatusInactive,
Address: item.GetAddress().String(),
ID: item.GetID(),
PlanID: 0,
},
)
// If the subscription is a NodeSubscription and the duration is specified in hours (non-zero),
// delete the payout from the store and its associated indexes.
if s, ok := item.(*types.NodeSubscription); ok && s.Hours != 0 {
payout, found := k.GetPayout(ctx, item.GetID())
if !found {
// If the payout is not found, panic with an error indicating the missing payout.
panic(fmt.Errorf("payout for subscription %d does not exist", item.GetID()))
}
// Delete the payout and its associated indexes from the Store.
k.DeletePayout(ctx, payout.ID)
k.DeletePayoutForAccount(ctx, payout.GetAddress(), payout.ID)
k.DeletePayoutForNode(ctx, payout.GetNodeAddress(), payout.ID)
}
return false
})
// Return an empty ValidatorUpdate slice as no validator updates are needed for the end block.
return nil
}