-
Notifications
You must be signed in to change notification settings - Fork 138
/
distribution.go
219 lines (194 loc) · 8.58 KB
/
distribution.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
package keeper
import (
"fmt"
"strconv"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
transfertypes "github.com/cosmos/ibc-go/v3/modules/apps/transfer/types"
clienttypes "github.com/cosmos/ibc-go/v3/modules/core/02-client/types"
channeltypes "github.com/cosmos/ibc-go/v3/modules/core/04-channel/types"
"github.com/cosmos/interchain-security/x/ccv/consumer/types"
ccv "github.com/cosmos/interchain-security/x/ccv/types"
)
// EndBlockRD executes EndBlock logic for the Reward Distribution sub-protocol.
// Reward Distribution follows a simple model: send tokens to the fee pool
// of the provider validator set
func (k Keeper) EndBlockRD(ctx sdk.Context) {
// Split blocks rewards.
// It panics in case of marshalling / unmarshalling errors or
// if sending coins between module accounts fails.
k.DistributeRewardsInternally(ctx)
if !k.shouldSendRewardsToProvider(ctx) {
return
}
// Try to send rewards to provider
cachedCtx, writeCache := ctx.CacheContext()
if err := k.SendRewardsToProvider(cachedCtx); err != nil {
k.Logger(ctx).Error("attempt to sent rewards to provider failed", "error", err)
} else {
// The cached context is created with a new EventManager so we merge the event
// into the original context
ctx.EventManager().EmitEvents(cachedCtx.EventManager().Events())
// write cache
writeCache()
}
// Update LastTransmissionBlockHeight
newLtbh := types.LastTransmissionBlockHeight{
Height: ctx.BlockHeight(),
}
k.SetLastTransmissionBlockHeight(ctx, newLtbh)
}
// DistributeRewardsInternally splits the block rewards according to the
// ConsumerRedistributionFrac param.
// Returns true if it's time to send rewards to provider
func (k Keeper) DistributeRewardsInternally(ctx sdk.Context) {
consumerFeePoolAddr := k.authKeeper.GetModuleAccount(ctx, k.feeCollectorName).GetAddress()
fpTokens := k.bankKeeper.GetAllBalances(ctx, consumerFeePoolAddr)
// split the fee pool, send the consumer's fraction to the consumer redistribution address
frac, err := sdk.NewDecFromStr(k.GetConsumerRedistributionFrac(ctx))
if err != nil {
// ConsumerRedistributionFrac was already validated when set as a param
panic(fmt.Errorf("ConsumerRedistributionFrac is invalid: %w", err))
}
decFPTokens := sdk.NewDecCoinsFromCoins(fpTokens...)
// NOTE the truncated decimal remainder will be sent to the provider fee pool
consRedistrTokens, _ := decFPTokens.MulDec(frac).TruncateDecimal()
err = k.bankKeeper.SendCoinsFromModuleToModule(ctx, k.feeCollectorName,
types.ConsumerRedistributeName, consRedistrTokens)
if err != nil {
// SendCoinsFromModuleToModule will panic if either module account does not exist,
// while SendCoins (called inside) returns an error upon failure.
// It is the common behavior in cosmos-sdk to panic if SendCoinsFromModuleToModule
// returns error.
panic(err)
}
// Send the remainder to the Provider fee pool over ibc. Buffer these
// through a secondary address on the consumer chain to ensure that the
// tokens do not go through the consumer redistribute split twice in the
// event that the transfer fails the tokens are returned to the consumer
// chain.
remainingTokens := fpTokens.Sub(consRedistrTokens)
err = k.bankKeeper.SendCoinsFromModuleToModule(ctx, k.feeCollectorName,
types.ConsumerToSendToProviderName, remainingTokens)
if err != nil {
// SendCoinsFromModuleToModule will panic if either module account does not exist,
// while SendCoins (called inside) returns an error upon failure.
// It is the common behavior in cosmos-sdk to panic if SendCoinsFromModuleToModule
// returns error.
panic(err)
}
}
// Check whether it's time to send rewards to provider
func (k Keeper) shouldSendRewardsToProvider(ctx sdk.Context) bool {
bpdt := k.GetBlocksPerDistributionTransmission(ctx)
curHeight := ctx.BlockHeight()
ltbh := k.GetLastTransmissionBlockHeight(ctx)
return (curHeight - ltbh.Height) >= bpdt
}
// SendRewardsToProvider attempts to send to the provider (via IBC)
// all the block rewards allocated for the provider
func (k Keeper) SendRewardsToProvider(ctx sdk.Context) error {
// empty out the toSendToProviderTokens address
ch := k.GetDistributionTransmissionChannel(ctx)
transferChannel, found := k.channelKeeper.GetChannel(ctx, transfertypes.PortID, ch)
if found && transferChannel.State == channeltypes.OPEN {
tstProviderAddr := k.authKeeper.GetModuleAccount(ctx,
types.ConsumerToSendToProviderName).GetAddress()
tstProviderTokens := k.bankKeeper.GetAllBalances(ctx, tstProviderAddr)
providerAddr := k.GetProviderFeePoolAddrStr(ctx)
timeoutHeight := clienttypes.ZeroHeight()
transferTimeoutPeriod := k.GetTransferTimeoutPeriod(ctx)
timeoutTimestamp := uint64(ctx.BlockTime().Add(transferTimeoutPeriod).UnixNano())
for _, token := range tstProviderTokens {
err := k.ibcTransferKeeper.SendTransfer(ctx,
transfertypes.PortID,
ch,
token,
tstProviderAddr,
providerAddr,
timeoutHeight,
timeoutTimestamp,
)
if err != nil {
return err
}
}
consumerFeePoolAddr := k.authKeeper.GetModuleAccount(ctx, k.feeCollectorName).GetAddress()
fpTokens := k.bankKeeper.GetAllBalances(ctx, consumerFeePoolAddr)
k.Logger(ctx).Info("sent block rewards to provider",
"total fee pool", fpTokens.String(),
"sent", tstProviderTokens.String(),
)
currentHeight := ctx.BlockHeight()
ctx.EventManager().EmitEvent(
sdk.NewEvent(
ccv.EventTypeFeeDistribution,
sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName),
sdk.NewAttribute(ccv.AttributeDistributionCurrentHeight, strconv.Itoa(int(currentHeight))),
sdk.NewAttribute(ccv.AttributeDistributionNextHeight, strconv.Itoa(int(currentHeight+k.GetBlocksPerDistributionTransmission(ctx)))),
sdk.NewAttribute(ccv.AttributeDistributionFraction, (k.GetConsumerRedistributionFrac(ctx))),
sdk.NewAttribute(ccv.AttributeDistributionTotal, fpTokens.String()),
sdk.NewAttribute(ccv.AttributeDistributionToProvider, tstProviderTokens.String()),
),
)
}
return nil
}
func (k Keeper) GetLastTransmissionBlockHeight(ctx sdk.Context) types.LastTransmissionBlockHeight {
store := ctx.KVStore(k.storeKey)
bz := store.Get(types.LastDistributionTransmissionKey())
ltbh := types.LastTransmissionBlockHeight{}
if bz != nil {
if err := ltbh.Unmarshal(bz); err != nil {
panic(fmt.Errorf("failed to unmarshal LastTransmissionBlockHeight: %w", err))
}
}
return ltbh
}
func (k Keeper) SetLastTransmissionBlockHeight(ctx sdk.Context, ltbh types.LastTransmissionBlockHeight) {
store := ctx.KVStore(k.storeKey)
bz, err := ltbh.Marshal()
if err != nil {
panic(fmt.Errorf("failed to marshal LastTransmissionBlockHeight: %w", err))
}
store.Set(types.LastDistributionTransmissionKey(), bz)
}
func (k Keeper) ChannelOpenInit(ctx sdk.Context, msg *channeltypes.MsgChannelOpenInit) (
*channeltypes.MsgChannelOpenInitResponse, error) {
return k.ibcCoreKeeper.ChannelOpenInit(sdk.WrapSDKContext(ctx), msg)
}
func (k Keeper) GetConnectionHops(ctx sdk.Context, srcPort, srcChan string) ([]string, error) {
ch, found := k.channelKeeper.GetChannel(ctx, srcPort, srcChan)
if !found {
return []string{}, sdkerrors.Wrapf(ccv.ErrChannelNotFound,
"cannot get connection hops from non-existent channel")
}
return ch.ConnectionHops, nil
}
// GetEstimatedNextFeeDistribution returns data about next fee distribution. Data represents an estimation of
// accumulated fees at the current block height.
func (k Keeper) GetEstimatedNextFeeDistribution(ctx sdk.Context) types.NextFeeDistributionEstimate {
lastH := k.GetLastTransmissionBlockHeight(ctx)
nextH := lastH.GetHeight() + k.GetBlocksPerDistributionTransmission(ctx)
consumerFeePoolAddr := k.authKeeper.GetModuleAccount(ctx, k.feeCollectorName).GetAddress()
total := k.bankKeeper.GetAllBalances(ctx, consumerFeePoolAddr)
fracParam := k.GetConsumerRedistributionFrac(ctx)
frac, err := sdk.NewDecFromStr(fracParam)
if err != nil {
// ConsumerRedistributionFrac was already validated when set as a param
panic(fmt.Errorf("ConsumerRedistributionFrac is invalid: %w", err))
}
totalTokens := sdk.NewDecCoinsFromCoins(total...)
// truncated decimals are implicitly added to provider
consumerTokens, _ := totalTokens.MulDec(frac).TruncateDecimal()
providerTokens := total.Sub(consumerTokens)
return types.NextFeeDistributionEstimate{
CurrentHeight: ctx.BlockHeight(),
LastHeight: lastH.GetHeight(),
NextHeight: nextH,
DistributionFraction: fracParam,
Total: totalTokens.String(),
ToProvider: sdk.NewDecCoinsFromCoins(providerTokens...).String(),
ToConsumer: sdk.NewDecCoinsFromCoins(consumerTokens...).String(),
}
}