github.com/cosmos/cosmos-sdk@v0.50.10/docs/architecture/adr-003-dynamic-capability-store.md (about)

     1  # ADR 3: Dynamic Capability Store
     2  
     3  ## Changelog
     4  
     5  * 12 December 2019: Initial version
     6  * 02 April 2020: Memory Store Revisions
     7  
     8  ## Context
     9  
    10  Full implementation of the [IBC specification](https://github.com/cosmos/ibc) requires the ability to create and authenticate object-capability keys at runtime (i.e., during transaction execution),
    11  as described in [ICS 5](https://github.com/cosmos/ibc/tree/master/spec/core/ics-005-port-allocation#technical-specification). In the IBC specification, capability keys are created for each newly initialised
    12  port & channel, and are used to authenticate future usage of the port or channel. Since channels and potentially ports can be initialised during transaction execution, the state machine must be able to create
    13  object-capability keys at this time.
    14  
    15  At present, the Cosmos SDK does not have the ability to do this. Object-capability keys are currently pointers (memory addresses) of `StoreKey` structs created at application initialisation in `app.go` ([example](https://github.com/cosmos/gaia/blob/dcbddd9f04b3086c0ad07ee65de16e7adedc7da4/app/app.go#L132))
    16  and passed to Keepers as fixed arguments ([example](https://github.com/cosmos/gaia/blob/dcbddd9f04b3086c0ad07ee65de16e7adedc7da4/app/app.go#L160)). Keepers cannot create or store capability keys during transaction execution — although they could call `NewKVStoreKey` and take the memory address
    17  of the returned struct, storing this in the Merklised store would result in a consensus fault, since the memory address will be different on each machine (this is intentional — were this not the case, the keys would be predictable and couldn't serve as object capabilities).
    18  
    19  Keepers need a way to keep a private map of store keys which can be altered during transaction execution, along with a suitable mechanism for regenerating the unique memory addresses (capability keys) in this map whenever the application is started or restarted, along with a mechanism to revert capability creation on tx failure.
    20  This ADR proposes such an interface & mechanism.
    21  
    22  ## Decision
    23  
    24  The Cosmos SDK will include a new `CapabilityKeeper` abstraction, which is responsible for provisioning,
    25  tracking, and authenticating capabilities at runtime. During application initialisation in `app.go`,
    26  the `CapabilityKeeper` will be hooked up to modules through unique function references
    27  (by calling `ScopeToModule`, defined below) so that it can identify the calling module when later
    28  invoked.
    29  
    30  When the initial state is loaded from disk, the `CapabilityKeeper`'s `Initialise` function will create
    31  new capability keys for all previously allocated capability identifiers (allocated during execution of
    32  past transactions and assigned to particular modes), and keep them in a memory-only store while the
    33  chain is running.
    34  
    35  The `CapabilityKeeper` will include a persistent `KVStore`, a `MemoryStore`, and an in-memory map.
    36  The persistent `KVStore` tracks which capability is owned by which modules.
    37  The `MemoryStore` stores a forward mapping that map from module name, capability tuples to capability names and
    38  a reverse mapping that map from module name, capability name to the capability index.
    39  Since we cannot marshal the capability into a `KVStore` and unmarshal without changing the memory location of the capability,
    40  the reverse mapping in the KVStore will simply map to an index. This index can then be used as a key in the ephemeral
    41  go-map to retrieve the capability at the original memory location.
    42  
    43  The `CapabilityKeeper` will define the following types & functions:
    44  
    45  The `Capability` is similar to `StoreKey`, but has a globally unique `Index()` instead of
    46  a name. A `String()` method is provided for debugging.
    47  
    48  A `Capability` is simply a struct, the address of which is taken for the actual capability.
    49  
    50  ```go
    51  type Capability struct {
    52    index uint64
    53  }
    54  ```
    55  
    56  A `CapabilityKeeper` contains a persistent store key, memory store key, and mapping of allocated module names.
    57  
    58  ```go
    59  type CapabilityKeeper struct {
    60    persistentKey StoreKey
    61    memKey        StoreKey
    62    capMap        map[uint64]*Capability
    63    moduleNames   map[string]interface{}
    64    sealed        bool
    65  }
    66  ```
    67  
    68  The `CapabilityKeeper` provides the ability to create *scoped* sub-keepers which are tied to a
    69  particular module name. These `ScopedCapabilityKeeper`s must be created at application initialisation
    70  and passed to modules, which can then use them to claim capabilities they receive and retrieve
    71  capabilities which they own by name, in addition to creating new capabilities & authenticating capabilities
    72  passed by other modules.
    73  
    74  ```go
    75  type ScopedCapabilityKeeper struct {
    76    persistentKey StoreKey
    77    memKey        StoreKey
    78    capMap        map[uint64]*Capability
    79    moduleName    string
    80  }
    81  ```
    82  
    83  `ScopeToModule` is used to create a scoped sub-keeper with a particular name, which must be unique.
    84  It MUST be called before `InitialiseAndSeal`.
    85  
    86  ```go
    87  func (ck CapabilityKeeper) ScopeToModule(moduleName string) ScopedCapabilityKeeper {
    88  	if k.sealed {
    89  		panic("cannot scope to module via a sealed capability keeper")
    90  	}
    91  
    92  	if _, ok := k.scopedModules[moduleName]; ok {
    93  		panic(fmt.Sprintf("cannot create multiple scoped keepers for the same module name: %s", moduleName))
    94  	}
    95  
    96  	k.scopedModules[moduleName] = struct{}{}
    97  
    98  	return ScopedKeeper{
    99  		cdc:      k.cdc,
   100  		storeKey: k.storeKey,
   101  		memKey:   k.memKey,
   102  		capMap:   k.capMap,
   103  		module:   moduleName,
   104  	}
   105  }
   106  ```
   107  
   108  `InitialiseAndSeal` MUST be called exactly once, after loading the initial state and creating all
   109  necessary `ScopedCapabilityKeeper`s, in order to populate the memory store with newly-created
   110  capability keys in accordance with the keys previously claimed by particular modules and prevent the
   111  creation of any new `ScopedCapabilityKeeper`s.
   112  
   113  ```go
   114  func (ck CapabilityKeeper) InitialiseAndSeal(ctx Context) {
   115    if ck.sealed {
   116      panic("capability keeper is sealed")
   117    }
   118  
   119    persistentStore := ctx.KVStore(ck.persistentKey)
   120    map := ctx.KVStore(ck.memKey)
   121    
   122    // initialise memory store for all names in persistent store
   123    for index, value := range persistentStore.Iter() {
   124      capability = &CapabilityKey{index: index}
   125  
   126      for moduleAndCapability := range value {
   127        moduleName, capabilityName := moduleAndCapability.Split("/")
   128        memStore.Set(moduleName + "/fwd/" + capability, capabilityName)
   129        memStore.Set(moduleName + "/rev/" + capabilityName, index)
   130  
   131        ck.capMap[index] = capability
   132      }
   133    }
   134  
   135    ck.sealed = true
   136  }
   137  ```
   138  
   139  `NewCapability` can be called by any module to create a new unique, unforgeable object-capability
   140  reference. The newly created capability is automatically persisted; the calling module need not
   141  call `ClaimCapability`.
   142  
   143  ```go
   144  func (sck ScopedCapabilityKeeper) NewCapability(ctx Context, name string) (Capability, error) {
   145    // check name not taken in memory store
   146    if capStore.Get("rev/" + name) != nil {
   147      return nil, errors.New("name already taken")
   148    }
   149  
   150    // fetch the current index
   151    index := persistentStore.Get("index")
   152    
   153    // create a new capability
   154    capability := &CapabilityKey{index: index}
   155    
   156    // set persistent store
   157    persistentStore.Set(index, Set.singleton(sck.moduleName + "/" + name))
   158    
   159    // update the index
   160    index++
   161    persistentStore.Set("index", index)
   162    
   163    // set forward mapping in memory store from capability to name
   164    memStore.Set(sck.moduleName + "/fwd/" + capability, name)
   165    
   166    // set reverse mapping in memory store from name to index
   167    memStore.Set(sck.moduleName + "/rev/" + name, index)
   168  
   169    // set the in-memory mapping from index to capability pointer
   170    capMap[index] = capability
   171    
   172    // return the newly created capability
   173    return capability
   174  }
   175  ```
   176  
   177  `AuthenticateCapability` can be called by any module to check that a capability
   178  does in fact correspond to a particular name (the name can be untrusted user input)
   179  with which the calling module previously associated it.
   180  
   181  ```go
   182  func (sck ScopedCapabilityKeeper) AuthenticateCapability(name string, capability Capability) bool {
   183    // return whether forward mapping in memory store matches name
   184    return memStore.Get(sck.moduleName + "/fwd/" + capability) === name
   185  }
   186  ```
   187  
   188  `ClaimCapability` allows a module to claim a capability key which it has received from another module
   189  so that future `GetCapability` calls will succeed.
   190  
   191  `ClaimCapability` MUST be called if a module which receives a capability wishes to access it by name
   192  in the future. Capabilities are multi-owner, so if multiple modules have a single `Capability` reference,
   193  they will all own it.
   194  
   195  ```go
   196  func (sck ScopedCapabilityKeeper) ClaimCapability(ctx Context, capability Capability, name string) error {
   197    persistentStore := ctx.KVStore(sck.persistentKey)
   198  
   199    // set forward mapping in memory store from capability to name
   200    memStore.Set(sck.moduleName + "/fwd/" + capability, name)
   201  
   202    // set reverse mapping in memory store from name to capability
   203    memStore.Set(sck.moduleName + "/rev/" + name, capability)
   204  
   205    // update owner set in persistent store
   206    owners := persistentStore.Get(capability.Index())
   207    owners.add(sck.moduleName + "/" + name)
   208    persistentStore.Set(capability.Index(), owners)
   209  }
   210  ```
   211  
   212  `GetCapability` allows a module to fetch a capability which it has previously claimed by name.
   213  The module is not allowed to retrieve capabilities which it does not own.
   214  
   215  ```go
   216  func (sck ScopedCapabilityKeeper) GetCapability(ctx Context, name string) (Capability, error) {
   217    // fetch the index of capability using reverse mapping in memstore
   218    index := memStore.Get(sck.moduleName + "/rev/" + name)
   219  
   220    // fetch capability from go-map using index
   221    capability := capMap[index]
   222  
   223    // return the capability
   224    return capability
   225  }
   226  ```
   227  
   228  `ReleaseCapability` allows a module to release a capability which it had previously claimed. If no
   229  more owners exist, the capability will be deleted globally.
   230  
   231  ```go
   232  func (sck ScopedCapabilityKeeper) ReleaseCapability(ctx Context, capability Capability) err {
   233    persistentStore := ctx.KVStore(sck.persistentKey)
   234  
   235    name := capStore.Get(sck.moduleName + "/fwd/" + capability)
   236    if name == nil {
   237      return error("capability not owned by module")
   238    }
   239  
   240    // delete forward mapping in memory store
   241    memoryStore.Delete(sck.moduleName + "/fwd/" + capability, name)
   242  
   243    // delete reverse mapping in memory store
   244    memoryStore.Delete(sck.moduleName + "/rev/" + name, capability)
   245  
   246    // update owner set in persistent store
   247    owners := persistentStore.Get(capability.Index())
   248    owners.remove(sck.moduleName + "/" + name)
   249    if owners.size() > 0 {
   250      // there are still other owners, keep the capability around
   251      persistentStore.Set(capability.Index(), owners)
   252    } else {
   253      // no more owners, delete the capability
   254      persistentStore.Delete(capability.Index())
   255      delete(capMap[capability.Index()])
   256    }
   257  }
   258  ```
   259  
   260  ### Usage patterns
   261  
   262  #### Initialisation
   263  
   264  Any modules which use dynamic capabilities must be provided a `ScopedCapabilityKeeper` in `app.go`:
   265  
   266  ```go
   267  ck := NewCapabilityKeeper(persistentKey, memoryKey)
   268  mod1Keeper := NewMod1Keeper(ck.ScopeToModule("mod1"), ....)
   269  mod2Keeper := NewMod2Keeper(ck.ScopeToModule("mod2"), ....)
   270  
   271  // other initialisation logic ...
   272  
   273  // load initial state...
   274  
   275  ck.InitialiseAndSeal(initialContext)
   276  ```
   277  
   278  #### Creating, passing, claiming and using capabilities
   279  
   280  Consider the case where `mod1` wants to create a capability, associate it with a resource (e.g. an IBC channel) by name, then pass it to `mod2` which will use it later:
   281  
   282  Module 1 would have the following code:
   283  
   284  ```go
   285  capability := scopedCapabilityKeeper.NewCapability(ctx, "resourceABC")
   286  mod2Keeper.SomeFunction(ctx, capability, args...)
   287  ```
   288  
   289  `SomeFunction`, running in module 2, could then claim the capability:
   290  
   291  ```go
   292  func (k Mod2Keeper) SomeFunction(ctx Context, capability Capability) {
   293    k.sck.ClaimCapability(ctx, capability, "resourceABC")
   294    // other logic...
   295  }
   296  ```
   297  
   298  Later on, module 2 can retrieve that capability by name and pass it to module 1, which will authenticate it against the resource:
   299  
   300  ```go
   301  func (k Mod2Keeper) SomeOtherFunction(ctx Context, name string) {
   302    capability := k.sck.GetCapability(ctx, name)
   303    mod1.UseResource(ctx, capability, "resourceABC")
   304  }
   305  ```
   306  
   307  Module 1 will then check that this capability key is authenticated to use the resource before allowing module 2 to use it:
   308  
   309  ```go
   310  func (k Mod1Keeper) UseResource(ctx Context, capability Capability, resource string) {
   311    if !k.sck.AuthenticateCapability(name, capability) {
   312      return errors.New("unauthenticated")
   313    }
   314    // do something with the resource
   315  }
   316  ```
   317  
   318  If module 2 passed the capability key to module 3, module 3 could then claim it and call module 1 just like module 2 did
   319  (in which case module 1, module 2, and module 3 would all be able to use this capability).
   320  
   321  ## Status
   322  
   323  Proposed.
   324  
   325  ## Consequences
   326  
   327  ### Positive
   328  
   329  * Dynamic capability support.
   330  * Allows CapabilityKeeper to return same capability pointer from go-map while reverting any writes to the persistent `KVStore` and in-memory `MemoryStore` on tx failure.
   331  
   332  ### Negative
   333  
   334  * Requires an additional keeper.
   335  * Some overlap with existing `StoreKey` system (in the future they could be combined, since this is a superset functionality-wise).
   336  * Requires an extra level of indirection in the reverse mapping, since MemoryStore must map to index which must then be used as key in a go map to retrieve the actual capability
   337  
   338  ### Neutral
   339  
   340  (none known)
   341  
   342  ## References
   343  
   344  * [Original discussion](https://github.com/cosmos/cosmos-sdk/pull/5230#discussion_r343978513)