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)