github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/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  
     7  ## Context
     8  
     9  Full implementation of the [IBC specification](https://github.com/cosmos/ics) requires the ability to create and authenticate object-capability keys at runtime (i.e., during transaction execution),
    10  as described in [ICS 5](https://github.com/cosmos/ics/tree/master/spec/ics-005-port-allocation#technical-specification). In the IBC specification, capability keys are created for each newly initialised
    11  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
    12  object-capability keys at this time.
    13  
    14  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))
    15  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
    16  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).
    17  
    18  Keepers need a way to keep a private map of store keys which can be altered during transacton execution, along with a suitable mechanism for regenerating the unique memory addresses (capability keys) in this map whenever the application is started or restarted.
    19  This ADR proposes such an interface & mechanism.
    20  
    21  ## Decision
    22  
    23  The SDK will include a new `CapabilityKeeper` abstraction, which is responsible for provisioning, tracking, and authenticating capabilities at runtime. During application initialisation in `app.go`, the `CapabilityKeeper` will
    24  be hooked up to modules through unique function references (by calling `ScopeToModule`, defined below) so that it can identify the calling module when later invoked. When the initial state is loaded from disk, the `CapabilityKeeper`'s `Initialise` function will create new capability keys
    25  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 SDK will include a new `MemoryStore` store type, similar
    26  to the existing `TransientStore` but without erasure on `Commit()`, which this `CapabilityKeeper` will use to privately store capability keys.
    27  
    28  The `CapabilityKeeper` will use two stores: a regular, persistent `KVStore`, which will track what capabilities have been created by each module, and an in-memory `MemoryStore` (described below), which will
    29  store the actual capabilities. The `CapabilityKeeper` will define the following types & functions:
    30  
    31  The `Capability` interface is similar to `StoreKey`, but has a globally unique `Index()` instead of a name. A `String()` method is provided for debugging.
    32  
    33  ```golang
    34  type Capability interface {
    35    Index() uint64
    36    String() string
    37  }
    38  ```
    39  
    40  A `CapabilityKey` is simply a struct, the address of which is taken for the actual capability.
    41  
    42  ```golang
    43  type CapabilityKey struct {
    44    name string
    45  }
    46  ```
    47  
    48  A `CapabilityKeeper` contains a persistent store key, memory store key, and mapping of allocated module names.
    49  
    50  ```golang
    51  type CapabilityKeeper struct {
    52    persistentKey StoreKey
    53    memoryKey MemoryStoreKey
    54    moduleNames map[string]interface{}
    55    sealed bool
    56  }
    57  ```
    58  
    59  The `CapabilityKeeper` provides the ability to create *scoped* sub-keepers which are tied to a particular module name. These `ScopedCapabilityKeeper`s must be created at application
    60  initialisation and passed to modules, which can then use them to claim capabilities they receive and retrieve capabilities which they own by name, in addition
    61  to creating new capabilities & authenticating capabilities passed by other modules.
    62  
    63  ```golang
    64  type ScopedCapabilityKeeper struct {
    65    persistentKey StoreKey
    66    memoryKey MemoryStoreKey
    67    moduleName string
    68  }
    69  ```
    70  
    71  `ScopeToModule` is used to create a scoped sub-keeper with a particular name, which must be unique. It MUST be called before `InitialiseAndSeal`.
    72  
    73  ```golang
    74  func (ck CapabilityKeeper) ScopeToModule(moduleName string) ScopedCapabilityKeeper {
    75    if ck.sealed {
    76      panic("capability keeper is sealed")
    77    }
    78    if _, present := ck.moduleNames[moduleName]; present {
    79      panic("cannot create multiple scoped capability keepers for the same module name")
    80    }
    81    ck.moduleNames[moduleName] = struct{}{}
    82    return ScopedCapabilityKeeper{
    83      persistentKey: ck.persistentKey,
    84      memoryKey: ck.memoryKey,
    85      moduleName: moduleName
    86    }
    87  }
    88  ```
    89  
    90  `InitialiseAndSeal` MUST be called exactly once, after loading the initial state and creating all necessary `ScopedCapabilityKeeper`s,
    91  in order to populate the memory store with newly-created capability keys in accordance with the keys previously claimed by particular modules
    92  and prevent the creation of any new `ScopedCapabilityKeeper`s.
    93  
    94  ```golang
    95  func (ck CapabilityKeeper) InitialiseAndSeal(ctx Context) {
    96    if ck.sealed {
    97      panic("capability keeper is sealed")
    98    }
    99    persistentStore := ctx.KVStore(ck.persistentKey)
   100    memoryStore := ctx.KVStore(ck.memoryKey)
   101    // initialise memory store for all names in persistent store
   102    for index, value := range persistentStore.Iter() {
   103      capability = &CapabilityKey{index: index}
   104      for moduleAndCapability := range value {
   105        moduleName, capabilityName := moduleAndCapability.Split("/")
   106        memoryStore.Set(moduleName + "/fwd/" + capability, capabilityName)
   107        memoryStore.Set(moduleName + "/rev/" + capabilityName, capability)
   108      }
   109    }
   110    ck.sealed = true
   111  }
   112  ```
   113  
   114  `NewCapability` can be called by any module to create a new unique, unforgeable object-capability
   115  reference. The newly created capability is automatically persisted; the calling module need not
   116  call `ClaimCapability`.
   117  
   118  ```golang
   119  func (sck ScopedCapabilityKeeper) NewCapability(ctx Context, name string) (Capability, error) {
   120    memoryStore := ctx.KVStore(sck.memoryKey)
   121    // check name not taken in memory store
   122    if memoryStore.Get("rev/" + name) != nil {
   123      return nil, errors.New("name already taken")
   124    }
   125    // fetch the current index
   126    index := persistentStore.Get("index")
   127    // create a new capability
   128    capability := &CapabilityKey{index: index}
   129    // set persistent store
   130    persistentStore.Set(index, Set.singleton(sck.moduleName + "/" + name))
   131    // update the index
   132    index++
   133    persistentStore.Set("index", index)
   134    // set forward mapping in memory store from capability to name
   135    memoryStore.Set(sck.moduleName + "/fwd/" + capability, name)
   136    // set reverse mapping in memory store from name to capability
   137    memoryStore.Set(sck.moduleName + "/rev/" + name, capability)
   138    // return the newly created capability
   139    return capability
   140  }
   141  ```
   142  
   143  `AuthenticateCapability` can be called by any module to check that a capability
   144  does in fact correspond to a particular name (the name can be untrusted user input)
   145  with which the calling module previously associated it.
   146  
   147  ```golang
   148  func (sck ScopedCapabilityKeeper) AuthenticateCapability(name string, capability Capability) bool {
   149    memoryStore := ctx.KVStore(sck.memoryKey)
   150    // return whether forward mapping in memory store matches name
   151    return memoryStore.Get(sck.moduleName + "/fwd/" + capability) === name
   152  }
   153  ```
   154  
   155  `ClaimCapability` allows a module to claim a capability key which it has received from another module so that future `GetCapability` calls will succeed.
   156  
   157  `ClaimCapability` MUST be called if a module which receives a capability wishes to access it by name in the future. Capabilities are multi-owner, so if multiple modules have a single `Capability` reference, they will all own it.
   158  
   159  ```golang
   160  func (sck ScopedCapabilityKeeper) ClaimCapability(ctx Context, capability Capability, name string) error {
   161    persistentStore := ctx.KVStore(sck.persistentKey)
   162    memoryStore := ctx.KVStore(sck.memoryKey)
   163    // set forward mapping in memory store from capability to name
   164    memoryStore.Set(sck.moduleName + "/fwd/" + capability, name)
   165    // set reverse mapping in memory store from name to capability
   166    memoryStore.Set(sck.moduleName + "/rev/" + name, capability)
   167    // update owner set in persistent store
   168    owners := persistentStore.Get(capability.Index())
   169    owners.add(sck.moduleName + "/" + name)
   170    persistentStore.Set(capability.Index(), owners)
   171  }
   172  ```
   173  
   174  `GetCapability` allows a module to fetch a capability which it has previously claimed by name. The module is not allowed to retrieve capabilities which it does not own. If another module
   175  claims a capability, the previously owning module will no longer be able to claim it.
   176  
   177  ```golang
   178  func (sck ScopedCapabilityKeeper) GetCapability(ctx Context, name string) (Capability, error) {
   179    memoryStore := ctx.KVStore(sck.memoryKey)
   180    // fetch capability from memory store
   181    capability := memoryStore.Get(sck.moduleName + "/rev/" + name)
   182    // return the capability
   183    return capability
   184  }
   185  ```
   186  
   187  ### Memory store
   188  
   189  A new store key type, `MemoryStoreKey`, will be added to the `store` package. The `MemoryStoreKey`s work just like `StoreKey`s.
   190  
   191  The memory store will work just like the current transient store, except that it will not create a new `dbadapter.Store` when `Commit()` is called, but instead retain the current one (so that state will persist across blocks).
   192  
   193  Initially the memory store will only be used by the `CapabilityKeeper`, but it could be used by other modules in the future.
   194  
   195  ### Usage patterns
   196  
   197  #### Initialisation
   198  
   199  Any modules which use dynamic capabilities must be provided a `ScopedCapabilityKeeper` in `app.go`:
   200  
   201  ```golang
   202  ck := NewCapabilityKeeper(persistentKey, memoryKey)
   203  mod1Keeper := NewMod1Keeper(ck.ScopeToModule("mod1"), ....)
   204  mod2Keeper := NewMod2Keeper(ck.ScopeToModule("mod2"), ....)
   205  
   206  // other initialisation logic ...
   207  
   208  // load initial state...
   209  
   210  ck.InitialiseAndSeal(initialContext)
   211  ```
   212  
   213  #### Creating, passing, claiming and using capabilities
   214  
   215  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:
   216  
   217  Module 1 would have the following code:
   218  
   219  ```golang
   220  capability := scopedCapabilityKeeper.NewCapability(ctx, "resourceABC")
   221  mod2Keeper.SomeFunction(ctx, capability, args...)
   222  ```
   223  
   224  `SomeFunction`, running in module 2, could then claim the capability:
   225  
   226  ```golang
   227  func (k Mod2Keeper) SomeFunction(ctx Context, capability Capability) {
   228    k.sck.ClaimCapability(ctx, capability, "resourceABC")
   229    // other logic...
   230  }
   231  ```
   232  
   233  Later on, module 2 can retrieve that capability by name and pass it to module 1, which will authenticate it against the resource:
   234  
   235  ```golang
   236  func (k Mod2Keeper) SomeOtherFunction(ctx Context, name string) {
   237    capability := k.sck.GetCapability(ctx, name)
   238    mod1.UseResource(ctx, capability, "resourceABC")
   239  }
   240  ```
   241  
   242  Module 1 will then check that this capability key is authenticated to use the resource before allowing module 2 to use it:
   243  
   244  ```golang
   245  func (k Mod1Keeper) UseResource(ctx Context, capability Capability, resource string) {
   246    if !k.sck.AuthenticateCapability(name, capability) {
   247      return errors.New("unauthenticated")
   248    }
   249    // do something with the resource
   250  }
   251  ```
   252  
   253  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
   254  (in which case module 1, module 2, and module 3 would all be able to use this capability).
   255  
   256  ## Status
   257  
   258  Proposed.
   259  
   260  ## Consequences
   261  
   262  ### Positive
   263  
   264  - Dynamic capability support.
   265  
   266  ### Negative
   267  
   268  - Requires an additional keeper.
   269  - Some overlap with existing `StoreKey` system (in the future they could be combined, since this is a superset functionality-wise).
   270  
   271  ### Neutral
   272  
   273  (none known)
   274  
   275  ## References
   276  
   277  - [Original discussion](https://github.com/cosmos/cosmos-sdk/pull/5230#discussion_r343978513)