forked from cosmos/cosmos-sdk
-
Notifications
You must be signed in to change notification settings - Fork 11
/
keeper.go
494 lines (406 loc) · 17.9 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
494
package keeper
import (
"fmt"
"strings"
"github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/capability/types"
)
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.BinaryCodec
storeKey storetypes.StoreKey
memKey storetypes.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.BinaryCodec
storeKey storetypes.StoreKey
memKey storetypes.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.BinaryCodec, storeKey, memKey storetypes.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,
}
}
// Seal seals the keeper to prevent further modules from creating a scoped keeper.
// Seal may be called during app initialization for applications that do not wish to create scoped keepers dynamically.
func (k *Keeper) Seal() {
if k.sealed {
panic("cannot initialize and seal an already sealed capability keeper")
}
k.sealed = true
}
// IsSealed returns if the keeper is sealed.
func (k *Keeper) IsSealed() bool {
return k.sealed
}
// InitMemStore will assure that the module store is a memory store (it will panic if it's not)
// and willl initialize it. The function is safe to be called multiple times.
// InitMemStore must be called every time the app starts before the keeper is used (so
// `BeginBlock` or `InitChain` - whichever is first). We need access to the store so we
// can't initialize it in a constructor.
func (k *Keeper) InitMemStore(ctx sdk.Context) {
memStore := ctx.KVStore(k.memKey)
memStoreType := memStore.GetStoreType()
if memStoreType != storetypes.StoreTypeMemory {
panic(fmt.Sprintf("invalid memory store type; got %s, expected: %s", memStoreType, storetypes.StoreTypeMemory))
}
// create context with no block gas meter to ensure we do not consume gas during local initialization logic.
noGasCtx := ctx.WithBlockGasMeter(sdk.NewInfiniteGasMeter())
// check if memory store has not been initialized yet by checking if initialized flag is nil.
if !k.IsInitialized(noGasCtx) {
prefixStore := prefix.NewStore(noGasCtx.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.MustUnmarshal(iterator.Value(), &capOwners)
k.InitializeCapability(noGasCtx, index, capOwners)
}
// set the initialized flag so we don't rerun initialization logic
memStore := noGasCtx.KVStore(k.memKey)
memStore.Set(types.KeyMemInitialized, []byte{1})
}
}
// IsInitialized returns true if the keeper is properly initialized, and false otherwise.
func (k *Keeper) IsInitialized(ctx sdk.Context) bool {
memStore := ctx.KVStore(k.memKey)
return memStore.Get(types.KeyMemInitialized) != nil
}
// 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.MustMarshal(&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.MustUnmarshal(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.MustMarshal(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) {
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.MustUnmarshal(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.MustMarshal(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.MustUnmarshal(bz, &capOwners)
return &capOwners
}
func logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
}