-
Notifications
You must be signed in to change notification settings - Fork 3.7k
/
keeper.go
493 lines (405 loc) · 18 KB
/
keeper.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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
package keeper
import (
"fmt"
"strings"
"github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/capability/types"
)
// initialized is a global variable used by GetCapability to ensure that the memory store
// and capability map are correctly populated. A state-synced node may copy over all the persistent
// state and start running the application without having the in-memory state required for x/capability.
// Thus, we must initialized the memory stores on-the-fly during tx execution once the first GetCapability
// is called.
// This is a temporary fix and should be replaced by a more robust solution in the next breaking release.
var initialized = false
type (
// Keeper defines the capability module's keeper. It is responsible for provisioning,
// tracking, and authenticating capabilities at runtime. During application
// initialization, the keeper can be hooked up to modules through unique function
// references so that it can identify the calling module when later invoked.
//
// When the initial state is loaded from disk, the keeper allows the ability to
// create new capability keys for all previously allocated capability identifiers
// (allocated during execution of past transactions and assigned to particular modes),
// and keep them in a memory-only store while the chain is running.
//
// The keeper allows the ability to create scoped sub-keepers which are tied to
// a single specific module.
Keeper struct {
cdc codec.BinaryMarshaler
storeKey sdk.StoreKey
memKey sdk.StoreKey
capMap map[uint64]*types.Capability
scopedModules map[string]struct{}
sealed bool
}
// ScopedKeeper defines a scoped sub-keeper which is tied to a single specific
// module provisioned by the capability keeper. Scoped keepers must be created
// at application initialization and passed to modules, which can then use them
// to claim capabilities they receive and retrieve capabilities which they own
// by name, in addition to creating new capabilities & authenticating capabilities
// passed by other modules.
ScopedKeeper struct {
cdc codec.BinaryMarshaler
storeKey sdk.StoreKey
memKey sdk.StoreKey
capMap map[uint64]*types.Capability
module string
}
)
// NewKeeper constructs a new CapabilityKeeper instance and initializes maps
// for capability map and scopedModules map.
func NewKeeper(cdc codec.BinaryMarshaler, storeKey, memKey sdk.StoreKey) *Keeper {
return &Keeper{
cdc: cdc,
storeKey: storeKey,
memKey: memKey,
capMap: make(map[uint64]*types.Capability),
scopedModules: make(map[string]struct{}),
sealed: false,
}
}
// ScopeToModule attempts to create and return a ScopedKeeper for a given module
// by name. It will panic if the keeper is already sealed or if the module name
// already has a ScopedKeeper.
func (k *Keeper) ScopeToModule(moduleName string) ScopedKeeper {
if k.sealed {
panic("cannot scope to module via a sealed capability keeper")
}
if strings.TrimSpace(moduleName) == "" {
panic("cannot scope to an empty module name")
}
if _, ok := k.scopedModules[moduleName]; ok {
panic(fmt.Sprintf("cannot create multiple scoped keepers for the same module name: %s", moduleName))
}
k.scopedModules[moduleName] = struct{}{}
return ScopedKeeper{
cdc: k.cdc,
storeKey: k.storeKey,
memKey: k.memKey,
capMap: k.capMap,
module: moduleName,
}
}
// InitializeAndSeal loads all capabilities from the persistent KVStore into the
// in-memory store and seals the keeper to prevent further modules from creating
// a scoped keeper. InitializeAndSeal must be called once after the application
// state is loaded.
func (k *Keeper) InitializeAndSeal(ctx sdk.Context) {
if k.sealed {
panic("cannot initialize and seal an already sealed capability keeper")
}
memStore := ctx.KVStore(k.memKey)
memStoreType := memStore.GetStoreType()
if memStoreType != sdk.StoreTypeMemory {
panic(fmt.Sprintf("invalid memory store type; got %s, expected: %s", memStoreType, sdk.StoreTypeMemory))
}
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixIndexCapability)
iterator := sdk.KVStorePrefixIterator(prefixStore, nil)
// initialize the in-memory store for all persisted capabilities
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
index := types.IndexFromKey(iterator.Key())
var capOwners types.CapabilityOwners
k.cdc.MustUnmarshalBinaryBare(iterator.Value(), &capOwners)
k.InitializeCapability(ctx, index, capOwners)
}
k.sealed = true
}
// InitializeIndex sets the index to one (or greater) in InitChain according
// to the GenesisState. It must only be called once.
// It will panic if the provided index is 0, or if the index is already set.
func (k Keeper) InitializeIndex(ctx sdk.Context, index uint64) error {
if index == 0 {
panic("SetIndex requires index > 0")
}
latest := k.GetLatestIndex(ctx)
if latest > 0 {
panic("SetIndex requires index to not be set")
}
// set the global index to the passed index
store := ctx.KVStore(k.storeKey)
store.Set(types.KeyIndex, types.IndexToKey(index))
return nil
}
// GetLatestIndex returns the latest index of the CapabilityKeeper
func (k Keeper) GetLatestIndex(ctx sdk.Context) uint64 {
store := ctx.KVStore(k.storeKey)
return types.IndexFromKey(store.Get(types.KeyIndex))
}
// SetOwners set the capability owners to the store
func (k Keeper) SetOwners(ctx sdk.Context, index uint64, owners types.CapabilityOwners) {
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixIndexCapability)
indexKey := types.IndexToKey(index)
// set owners in persistent store
prefixStore.Set(indexKey, k.cdc.MustMarshalBinaryBare(&owners))
}
// GetOwners returns the capability owners with a given index.
func (k Keeper) GetOwners(ctx sdk.Context, index uint64) (types.CapabilityOwners, bool) {
prefixStore := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefixIndexCapability)
indexKey := types.IndexToKey(index)
// get owners for index from persistent store
ownerBytes := prefixStore.Get(indexKey)
if ownerBytes == nil {
return types.CapabilityOwners{}, false
}
var owners types.CapabilityOwners
k.cdc.MustUnmarshalBinaryBare(ownerBytes, &owners)
return owners, true
}
// InitializeCapability takes in an index and an owners array. It creates the capability in memory
// and sets the fwd and reverse keys for each owner in the memstore.
// It is used during initialization from genesis.
func (k Keeper) InitializeCapability(ctx sdk.Context, index uint64, owners types.CapabilityOwners) {
memStore := ctx.KVStore(k.memKey)
cap := types.NewCapability(index)
for _, owner := range owners.Owners {
// Set the forward mapping between the module and capability tuple and the
// capability name in the memKVStore
memStore.Set(types.FwdCapabilityKey(owner.Module, cap), []byte(owner.Name))
// Set the reverse mapping between the module and capability name and the
// index in the in-memory store. Since marshalling and unmarshalling into a store
// will change memory address of capability, we simply store index as value here
// and retrieve the in-memory pointer to the capability from our map
memStore.Set(types.RevCapabilityKey(owner.Module, owner.Name), sdk.Uint64ToBigEndian(index))
// Set the mapping from index from index to in-memory capability in the go map
k.capMap[index] = cap
}
}
// NewCapability attempts to create a new capability with a given name. If the
// capability already exists in the in-memory store, an error will be returned.
// Otherwise, a new capability is created with the current global unique index.
// The newly created capability has the scoped module name and capability name
// tuple set as the initial owner. Finally, the global index is incremented along
// with forward and reverse indexes set in the in-memory store.
//
// Note, namespacing is completely local, which is safe since records are prefixed
// with the module name and no two ScopedKeeper can have the same module name.
func (sk ScopedKeeper) NewCapability(ctx sdk.Context, name string) (*types.Capability, error) {
if strings.TrimSpace(name) == "" {
return nil, sdkerrors.Wrap(types.ErrInvalidCapabilityName, "capability name cannot be empty")
}
store := ctx.KVStore(sk.storeKey)
if _, ok := sk.GetCapability(ctx, name); ok {
return nil, sdkerrors.Wrapf(types.ErrCapabilityTaken, fmt.Sprintf("module: %s, name: %s", sk.module, name))
}
// create new capability with the current global index
index := types.IndexFromKey(store.Get(types.KeyIndex))
cap := types.NewCapability(index)
// update capability owner set
if err := sk.addOwner(ctx, cap, name); err != nil {
return nil, err
}
// increment global index
store.Set(types.KeyIndex, types.IndexToKey(index+1))
memStore := ctx.KVStore(sk.memKey)
// Set the forward mapping between the module and capability tuple and the
// capability name in the memKVStore
memStore.Set(types.FwdCapabilityKey(sk.module, cap), []byte(name))
// Set the reverse mapping between the module and capability name and the
// index in the in-memory store. Since marshalling and unmarshalling into a store
// will change memory address of capability, we simply store index as value here
// and retrieve the in-memory pointer to the capability from our map
memStore.Set(types.RevCapabilityKey(sk.module, name), sdk.Uint64ToBigEndian(index))
// Set the mapping from index from index to in-memory capability in the go map
sk.capMap[index] = cap
logger(ctx).Info("created new capability", "module", sk.module, "name", name)
return cap, nil
}
// AuthenticateCapability attempts to authenticate a given capability and name
// from a caller. It allows for a caller to check that a capability does in fact
// correspond to a particular name. The scoped keeper will lookup the capability
// from the internal in-memory store and check against the provided name. It returns
// true upon success and false upon failure.
//
// Note, the capability's forward mapping is indexed by a string which should
// contain its unique memory reference.
func (sk ScopedKeeper) AuthenticateCapability(ctx sdk.Context, cap *types.Capability, name string) bool {
if strings.TrimSpace(name) == "" || cap == nil {
return false
}
return sk.GetCapabilityName(ctx, cap) == name
}
// ClaimCapability attempts to claim a given Capability. The provided name and
// the scoped module's name tuple are treated as the owner. It will attempt
// to add the owner to the persistent set of capability owners for the capability
// index. If the owner already exists, it will return an error. Otherwise, it will
// also set a forward and reverse index for the capability and capability name.
func (sk ScopedKeeper) ClaimCapability(ctx sdk.Context, cap *types.Capability, name string) error {
if cap == nil {
return sdkerrors.Wrap(types.ErrNilCapability, "cannot claim nil capability")
}
if strings.TrimSpace(name) == "" {
return sdkerrors.Wrap(types.ErrInvalidCapabilityName, "capability name cannot be empty")
}
// update capability owner set
if err := sk.addOwner(ctx, cap, name); err != nil {
return err
}
memStore := ctx.KVStore(sk.memKey)
// Set the forward mapping between the module and capability tuple and the
// capability name in the memKVStore
memStore.Set(types.FwdCapabilityKey(sk.module, cap), []byte(name))
// Set the reverse mapping between the module and capability name and the
// index in the in-memory store. Since marshalling and unmarshalling into a store
// will change memory address of capability, we simply store index as value here
// and retrieve the in-memory pointer to the capability from our map
memStore.Set(types.RevCapabilityKey(sk.module, name), sdk.Uint64ToBigEndian(cap.GetIndex()))
logger(ctx).Info("claimed capability", "module", sk.module, "name", name, "capability", cap.GetIndex())
return nil
}
// ReleaseCapability allows a scoped module to release a capability which it had
// previously claimed or created. After releasing the capability, if no more
// owners exist, the capability will be globally removed.
func (sk ScopedKeeper) ReleaseCapability(ctx sdk.Context, cap *types.Capability) error {
if cap == nil {
return sdkerrors.Wrap(types.ErrNilCapability, "cannot release nil capability")
}
name := sk.GetCapabilityName(ctx, cap)
if len(name) == 0 {
return sdkerrors.Wrap(types.ErrCapabilityNotOwned, sk.module)
}
memStore := ctx.KVStore(sk.memKey)
// Delete the forward mapping between the module and capability tuple and the
// capability name in the memKVStore
memStore.Delete(types.FwdCapabilityKey(sk.module, cap))
// Delete the reverse mapping between the module and capability name and the
// index in the in-memory store.
memStore.Delete(types.RevCapabilityKey(sk.module, name))
// remove owner
capOwners := sk.getOwners(ctx, cap)
capOwners.Remove(types.NewOwner(sk.module, name))
prefixStore := prefix.NewStore(ctx.KVStore(sk.storeKey), types.KeyPrefixIndexCapability)
indexKey := types.IndexToKey(cap.GetIndex())
if len(capOwners.Owners) == 0 {
// remove capability owner set
prefixStore.Delete(indexKey)
// since no one owns capability, we can delete capability from map
delete(sk.capMap, cap.GetIndex())
} else {
// update capability owner set
prefixStore.Set(indexKey, sk.cdc.MustMarshalBinaryBare(capOwners))
}
return nil
}
// GetCapability allows a module to fetch a capability which it previously claimed
// by name. The module is not allowed to retrieve capabilities which it does not
// own.
func (sk ScopedKeeper) GetCapability(ctx sdk.Context, name string) (*types.Capability, bool) {
// Create a keeper that will set all in-memory mappings correctly into memstore and capmap if scoped keeper is not initialized yet.
// This ensures that the in-memory mappings are correctly filled in, in case this is a state-synced node.
// This is a temporary non-breaking fix, a future PR should store the reverse mapping in the persistent store and reconstruct forward mapping and capmap on the fly.
if !initialized {
// create context with infinite gas meter to avoid app state mismatch.
initCtx := ctx.WithGasMeter(sdk.NewInfiniteGasMeter())
k := Keeper{
cdc: sk.cdc,
storeKey: sk.storeKey,
memKey: sk.memKey,
capMap: sk.capMap,
}
k.InitializeAndSeal(initCtx)
initialized = true
}
if strings.TrimSpace(name) == "" {
return nil, false
}
memStore := ctx.KVStore(sk.memKey)
key := types.RevCapabilityKey(sk.module, name)
indexBytes := memStore.Get(key)
index := sdk.BigEndianToUint64(indexBytes)
if len(indexBytes) == 0 {
// If a tx failed and NewCapability got reverted, it is possible
// to still have the capability in the go map since changes to
// go map do not automatically get reverted on tx failure,
// so we delete here to remove unnecessary values in map
// TODO: Delete index correctly from capMap by storing some reverse lookup
// in-memory map. Issue: https://github.com/cosmos/cosmos-sdk/issues/7805
return nil, false
}
cap := sk.capMap[index]
if cap == nil {
panic("capability found in memstore is missing from map")
}
return cap, true
}
// GetCapabilityName allows a module to retrieve the name under which it stored a given
// capability given the capability
func (sk ScopedKeeper) GetCapabilityName(ctx sdk.Context, cap *types.Capability) string {
if cap == nil {
return ""
}
memStore := ctx.KVStore(sk.memKey)
return string(memStore.Get(types.FwdCapabilityKey(sk.module, cap)))
}
// GetOwners all the Owners that own the capability associated with the name this ScopedKeeper uses
// to refer to the capability
func (sk ScopedKeeper) GetOwners(ctx sdk.Context, name string) (*types.CapabilityOwners, bool) {
if strings.TrimSpace(name) == "" {
return nil, false
}
cap, ok := sk.GetCapability(ctx, name)
if !ok {
return nil, false
}
prefixStore := prefix.NewStore(ctx.KVStore(sk.storeKey), types.KeyPrefixIndexCapability)
indexKey := types.IndexToKey(cap.GetIndex())
var capOwners types.CapabilityOwners
bz := prefixStore.Get(indexKey)
if len(bz) == 0 {
return nil, false
}
sk.cdc.MustUnmarshalBinaryBare(bz, &capOwners)
return &capOwners, true
}
// LookupModules returns all the module owners for a given capability
// as a string array and the capability itself.
// The method returns an error if either the capability or the owners cannot be
// retreived from the memstore.
func (sk ScopedKeeper) LookupModules(ctx sdk.Context, name string) ([]string, *types.Capability, error) {
if strings.TrimSpace(name) == "" {
return nil, nil, sdkerrors.Wrap(types.ErrInvalidCapabilityName, "cannot lookup modules with empty capability name")
}
cap, ok := sk.GetCapability(ctx, name)
if !ok {
return nil, nil, sdkerrors.Wrap(types.ErrCapabilityNotFound, name)
}
capOwners, ok := sk.GetOwners(ctx, name)
if !ok {
return nil, nil, sdkerrors.Wrap(types.ErrCapabilityOwnersNotFound, name)
}
mods := make([]string, len(capOwners.Owners))
for i, co := range capOwners.Owners {
mods[i] = co.Module
}
return mods, cap, nil
}
func (sk ScopedKeeper) addOwner(ctx sdk.Context, cap *types.Capability, name string) error {
prefixStore := prefix.NewStore(ctx.KVStore(sk.storeKey), types.KeyPrefixIndexCapability)
indexKey := types.IndexToKey(cap.GetIndex())
capOwners := sk.getOwners(ctx, cap)
if err := capOwners.Set(types.NewOwner(sk.module, name)); err != nil {
return err
}
// update capability owner set
prefixStore.Set(indexKey, sk.cdc.MustMarshalBinaryBare(capOwners))
return nil
}
func (sk ScopedKeeper) getOwners(ctx sdk.Context, cap *types.Capability) *types.CapabilityOwners {
prefixStore := prefix.NewStore(ctx.KVStore(sk.storeKey), types.KeyPrefixIndexCapability)
indexKey := types.IndexToKey(cap.GetIndex())
bz := prefixStore.Get(indexKey)
if len(bz) == 0 {
return types.NewCapabilityOwners()
}
var capOwners types.CapabilityOwners
sk.cdc.MustUnmarshalBinaryBare(bz, &capOwners)
return &capOwners
}
func logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
}