forked from babylonlabs-io/babylon
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfinality_providers.go
312 lines (266 loc) · 10.7 KB
/
finality_providers.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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
package keeper
import (
"context"
"fmt"
sdkmath "cosmossdk.io/math"
"cosmossdk.io/store/prefix"
"github.com/cosmos/cosmos-sdk/runtime"
sdk "github.com/cosmos/cosmos-sdk/types"
bbn "github.com/babylonlabs-io/babylon/types"
"github.com/babylonlabs-io/babylon/x/btcstaking/types"
)
// AddFinalityProvider adds the given finality provider to KVStore if it has valid
// commission and it was not inserted before
func (k Keeper) AddFinalityProvider(goCtx context.Context, msg *types.MsgCreateFinalityProvider) error {
ctx := sdk.UnwrapSDKContext(goCtx)
params := k.GetParams(ctx)
// ensure commission rate is
// - at least the minimum commission rate in parameters, and
// - at most 1
if msg.Commission.LT(params.MinCommissionRate) {
return types.ErrCommissionLTMinRate.Wrapf("cannot set finality provider commission to less than minimum rate of %s", params.MinCommissionRate.String())
}
if msg.Commission.GT(sdkmath.LegacyOneDec()) {
return types.ErrCommissionGTMaxRate
}
// ensure finality provider does not already exist
if k.HasFinalityProvider(ctx, *msg.BtcPk) {
return types.ErrFpRegistered
}
// default consumer ID is Babylon's chain ID
consumerID := msg.GetConsumerId()
if consumerID == "" {
// Babylon chain ID
consumerID = ctx.ChainID()
}
// all good, add this finality provider
fp := types.FinalityProvider{
Description: msg.Description,
Commission: msg.Commission,
Addr: msg.Addr,
BtcPk: msg.BtcPk,
Pop: msg.Pop,
ConsumerId: consumerID,
}
if consumerID == ctx.ChainID() {
k.setFinalityProvider(ctx, &fp)
} else {
if err := k.SetConsumerFinalityProvider(ctx, &fp, consumerID); err != nil {
return err
}
}
// notify subscriber
return ctx.EventManager().EmitTypedEvent(types.NewEventFinalityProviderCreated(&fp))
}
// setFinalityProvider adds the given finality provider to KVStore
func (k Keeper) setFinalityProvider(ctx context.Context, fp *types.FinalityProvider) {
store := k.finalityProviderStore(ctx)
fpBytes := k.cdc.MustMarshal(fp)
store.Set(fp.BtcPk.MustMarshal(), fpBytes)
}
// UpdateFinalityProvider update the given finality provider to KVStore
func (k Keeper) UpdateFinalityProvider(ctx context.Context, fp *types.FinalityProvider) error {
if !k.HasFinalityProvider(ctx, fp.BtcPk.MustMarshal()) {
return types.ErrFpNotFound
}
k.setFinalityProvider(ctx, fp)
return nil
}
// HasFinalityProvider checks if the finality provider exists
func (k Keeper) HasFinalityProvider(ctx context.Context, fpBTCPK []byte) bool {
store := k.finalityProviderStore(ctx)
return store.Has(fpBTCPK)
}
// GetFinalityProvider gets the finality provider with the given finality provider Bitcoin PK
func (k Keeper) GetFinalityProvider(ctx context.Context, fpBTCPK []byte) (*types.FinalityProvider, error) {
store := k.finalityProviderStore(ctx)
if !k.HasFinalityProvider(ctx, fpBTCPK) {
return nil, types.ErrFpNotFound
}
fpBytes := store.Get(fpBTCPK)
var fp types.FinalityProvider
k.cdc.MustUnmarshal(fpBytes, &fp)
return &fp, nil
}
// SlashFinalityProvider slashes a finality provider with the given PK
// A slashed finality provider will not have voting power
func (k Keeper) SlashFinalityProvider(ctx context.Context, fpBTCPK []byte) error {
// ensure finality provider exists
fp, err := k.GetFinalityProvider(ctx, fpBTCPK)
if err != nil {
return err
}
// ensure finality provider is not slashed yet
if fp.IsSlashed() {
return types.ErrFpAlreadySlashed
}
// set finality provider to be slashed
fp.SlashedBabylonHeight = uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height)
btcTip := k.btclcKeeper.GetTipInfo(ctx)
if btcTip == nil {
return fmt.Errorf("failed to get current BTC tip")
}
fp.SlashedBtcHeight = btcTip.Height
k.setFinalityProvider(ctx, fp)
// record slashed event. The next `BeginBlock` will consume this
// event for updating the finality provider set
powerUpdateEvent := types.NewEventPowerDistUpdateWithSlashedFP(fp.BtcPk)
k.addPowerDistUpdateEvent(ctx, btcTip.Height, powerUpdateEvent)
return nil
}
// SlashConsumerFinalityProvider slashes a consumer finality provider with the given PK
func (k Keeper) SlashConsumerFinalityProvider(ctx context.Context, consumerID string, fpBTCPK *bbn.BIP340PubKey) error {
// Get consumer finality provider
fp, err := k.BscKeeper.GetConsumerFinalityProvider(ctx, consumerID, fpBTCPK)
if err != nil {
return err
}
// Return error if already slashed
if fp.IsSlashed() {
return types.ErrFpAlreadySlashed
}
// Set slashed height
fp.SlashedBabylonHeight = uint64(sdk.UnwrapSDKContext(ctx).HeaderInfo().Height)
btcTip := k.btclcKeeper.GetTipInfo(ctx)
if btcTip == nil {
return fmt.Errorf("failed to get current BTC tip")
}
fp.SlashedBtcHeight = btcTip.Height
k.BscKeeper.SetConsumerFinalityProvider(ctx, fp)
// Process all delegations for this consumer finality provider and record slashed events
err = k.HandleFPBTCDelegations(ctx, fpBTCPK, func(btcDel *types.BTCDelegation) error {
stakingTxHash := btcDel.MustGetStakingTxHash().String()
eventSlashedBTCDelegation := types.NewEventPowerDistUpdateWithSlashedBTCDelegation(stakingTxHash)
k.addPowerDistUpdateEvent(ctx, btcTip.Height, eventSlashedBTCDelegation)
return nil
})
if err != nil {
return fmt.Errorf("failed to handle BTC delegations: %w", err)
}
return nil
}
// PropagateFPSlashingToConsumers propagates the slashing of a finality provider (FP) to all relevant consumer chains.
// It processes all delegations associated with the given FP and creates slashing events for each affected consumer chain.
//
// The function performs the following steps:
// 1. Retrieves all BTC delegations associated with the given finality provider.
// 2. Collects slashed events for each consumer chain using collectSlashedConsumerEvents:
// a. For each delegation, creates a SlashedBTCDelegation event.
// b. Identifies the consumer chains associated with the FPs in the delegation.
// c. Ensures that each consumer chain receives only one event per delegation, even if multiple FPs in the delegation belong to the same consumer.
// 3. Sends the collected events to their respective consumer chains.
//
// Parameters:
// - ctx: The context for the operation.
// - fpBTCPK: The Bitcoin public key of the finality provider being slashed.
//
// Returns:
// - An error if any operation fails, nil otherwise.
func (k Keeper) PropagateFPSlashingToConsumers(ctx context.Context, fpBTCPK *bbn.BIP340PubKey) error {
// Map to collect events for each consumer
consumerEvents := make(map[string][]*types.BTCStakingConsumerEvent)
// Create a map to store FP to consumer ID mappings
fpToConsumerMap := make(map[string]string)
// Process all delegations for this finality provider and collect slashing events
// for each consumer chain. Ensures that each consumer receives only one event per
// delegation, even if multiple finality providers in the delegation belong to the same consumer.
err := k.HandleFPBTCDelegations(ctx, fpBTCPK, func(delegation *types.BTCDelegation) error {
consumerEvent := types.CreateSlashedBTCDelegationEvent(delegation)
// Track consumers seen for this delegation
seenConsumers := make(map[string]struct{})
for _, delegationFPBTCPK := range delegation.FpBtcPkList {
fpBTCPKHex := delegationFPBTCPK.MarshalHex()
consumerID, exists := fpToConsumerMap[fpBTCPKHex]
if !exists {
// If not in map, check if it's a Babylon FP or get its consumer
// TODO: avoid querying GetFinalityProvider again by passing the result
// https://github.com/babylonlabs-io/babylon/blob/873f1232365573a97032037af4ac99b5e3fcada8/x/btcstaking/keeper/btc_delegators.go#L79 to this function
if _, err := k.GetFinalityProvider(ctx, delegationFPBTCPK); err == nil {
continue // It's a Babylon FP, skip
} else if consumerID, err = k.BscKeeper.GetConsumerOfFinalityProvider(ctx, &delegationFPBTCPK); err == nil {
// Found consumer, add to map
fpToConsumerMap[fpBTCPKHex] = consumerID
} else {
return types.ErrFpNotFound.Wrapf("finality provider pk %s is not found", fpBTCPKHex)
}
}
// Only add event once per consumer per delegation
if _, ok := seenConsumers[consumerID]; !ok {
consumerEvents[consumerID] = append(consumerEvents[consumerID], consumerEvent)
seenConsumers[consumerID] = struct{}{}
}
}
return nil
})
if err != nil {
return err
}
// Send collected events to each involved consumer chain
for consumerID, events := range consumerEvents {
if err := k.AddBTCStakingConsumerEvents(ctx, consumerID, events); err != nil {
return err
}
}
return nil
}
// JailFinalityProvider jails a finality provider with the given PK
// A jailed finality provider will not have voting power until it is
// unjailed (assuming it still ranks top N and has timestamped pub rand)
func (k Keeper) JailFinalityProvider(ctx context.Context, fpBTCPK []byte) error {
// ensure finality provider exists
fp, err := k.GetFinalityProvider(ctx, fpBTCPK)
if err != nil {
return err
}
// ensure finality provider is not slashed yet
if fp.IsSlashed() {
return types.ErrFpAlreadySlashed
}
// ensure finality provider is not jailed yet
if fp.IsJailed() {
return types.ErrFpAlreadyJailed
}
// set finality provider to be jailed
fp.Jailed = true
k.setFinalityProvider(ctx, fp)
btcTip := k.btclcKeeper.GetTipInfo(ctx)
if btcTip == nil {
return fmt.Errorf("failed to get current BTC tip")
}
// record jailed event. The next `BeginBlock` will consume this
// event for updating the finality provider set
powerUpdateEvent := types.NewEventPowerDistUpdateWithJailedFP(fp.BtcPk)
k.addPowerDistUpdateEvent(ctx, btcTip.Height, powerUpdateEvent)
return nil
}
// UnjailFinalityProvider reverts the Jailed flag of a finality provider
func (k Keeper) UnjailFinalityProvider(ctx context.Context, fpBTCPK []byte) error {
// ensure finality provider exists
fp, err := k.GetFinalityProvider(ctx, fpBTCPK)
if err != nil {
return err
}
// ensure finality provider is already jailed
if !fp.IsJailed() {
return types.ErrFpNotJailed
}
fp.Jailed = false
k.setFinalityProvider(ctx, fp)
btcTip := k.btclcKeeper.GetTipInfo(ctx)
if btcTip == nil {
return fmt.Errorf("failed to get current BTC tip")
}
// record unjailed event. The next `BeginBlock` will consume this
// event for updating the finality provider set
powerUpdateEvent := types.NewEventPowerDistUpdateWithUnjailedFP(fp.BtcPk)
k.addPowerDistUpdateEvent(ctx, btcTip.Height, powerUpdateEvent)
return nil
}
// finalityProviderStore returns the KVStore of the finality provider set
// prefix: FinalityProviderKey
// key: Bitcoin secp256k1 PK
// value: FinalityProvider object
func (k Keeper) finalityProviderStore(ctx context.Context) prefix.Store {
storeAdapter := runtime.KVStoreAdapter(k.storeService.OpenKVStore(ctx))
return prefix.NewStore(storeAdapter, types.FinalityProviderKey)
}