github.com/cosmos/cosmos-sdk@v0.50.10/x/params/types/subspace.go (about) 1 package types 2 3 import ( 4 "fmt" 5 "reflect" 6 7 "cosmossdk.io/store/prefix" 8 storetypes "cosmossdk.io/store/types" 9 10 "github.com/cosmos/cosmos-sdk/codec" 11 sdk "github.com/cosmos/cosmos-sdk/types" 12 ) 13 14 const ( 15 // StoreKey is the string store key for the param store 16 StoreKey = "params" 17 18 // TStoreKey is the string store key for the param transient store 19 TStoreKey = "transient_params" 20 ) 21 22 // Individual parameter store for each keeper 23 // Transient store persists for a block, so we use it for 24 // recording whether the parameter has been changed or not 25 type Subspace struct { 26 cdc codec.BinaryCodec 27 legacyAmino *codec.LegacyAmino 28 key storetypes.StoreKey // []byte -> []byte, stores parameter 29 tkey storetypes.StoreKey // []byte -> bool, stores parameter change 30 name []byte 31 table KeyTable 32 } 33 34 // NewSubspace constructs a store with namestore 35 func NewSubspace(cdc codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key, tkey storetypes.StoreKey, name string) Subspace { 36 return Subspace{ 37 cdc: cdc, 38 legacyAmino: legacyAmino, 39 key: key, 40 tkey: tkey, 41 name: []byte(name), 42 table: NewKeyTable(), 43 } 44 } 45 46 // HasKeyTable returns if the Subspace has a KeyTable registered. 47 func (s Subspace) HasKeyTable() bool { 48 return len(s.table.m) > 0 49 } 50 51 // WithKeyTable initializes KeyTable and returns modified Subspace 52 func (s Subspace) WithKeyTable(table KeyTable) Subspace { 53 if table.m == nil { 54 panic("WithKeyTable() called with nil KeyTable") 55 } 56 if len(s.table.m) != 0 { 57 panic("WithKeyTable() called on already initialized Subspace") 58 } 59 60 for k, v := range table.m { 61 s.table.m[k] = v 62 } 63 64 // Allocate additional capacity for Subspace.name 65 // So we don't have to allocate extra space each time appending to the key 66 name := s.name 67 s.name = make([]byte, len(name), len(name)+table.maxKeyLength()) 68 copy(s.name, name) 69 70 return s 71 } 72 73 // Returns a KVStore identical with ctx.KVStore(s.key).Prefix() 74 func (s Subspace) kvStore(ctx sdk.Context) storetypes.KVStore { 75 // append here is safe, appends within a function won't cause 76 // weird side effects when its singlethreaded 77 return prefix.NewStore(ctx.KVStore(s.key), append(s.name, '/')) 78 } 79 80 // Returns a transient store for modification 81 func (s Subspace) transientStore(ctx sdk.Context) storetypes.KVStore { 82 // append here is safe, appends within a function won't cause 83 // weird side effects when its singlethreaded 84 return prefix.NewStore(ctx.TransientStore(s.tkey), append(s.name, '/')) 85 } 86 87 // Validate attempts to validate a parameter value by its key. If the key is not 88 // registered or if the validation of the value fails, an error is returned. 89 func (s Subspace) Validate(ctx sdk.Context, key []byte, value interface{}) error { 90 attr, ok := s.table.m[string(key)] 91 if !ok { 92 return fmt.Errorf("parameter %s not registered", key) 93 } 94 95 if err := attr.vfn(value); err != nil { 96 return fmt.Errorf("invalid parameter value: %s", err) 97 } 98 99 return nil 100 } 101 102 // Get queries for a parameter by key from the Subspace's KVStore and sets the 103 // value to the provided pointer. If the value does not exist, it will panic. 104 func (s Subspace) Get(ctx sdk.Context, key []byte, ptr interface{}) { 105 s.checkType(key, ptr) 106 107 store := s.kvStore(ctx) 108 bz := store.Get(key) 109 110 if err := s.legacyAmino.UnmarshalJSON(bz, ptr); err != nil { 111 panic(err) 112 } 113 } 114 115 // GetIfExists queries for a parameter by key from the Subspace's KVStore and 116 // sets the value to the provided pointer. If the value does not exist, it will 117 // perform a no-op. 118 func (s Subspace) GetIfExists(ctx sdk.Context, key []byte, ptr interface{}) { 119 store := s.kvStore(ctx) 120 bz := store.Get(key) 121 if bz == nil { 122 return 123 } 124 125 s.checkType(key, ptr) 126 127 if err := s.legacyAmino.UnmarshalJSON(bz, ptr); err != nil { 128 panic(err) 129 } 130 } 131 132 // IterateKeys iterates over all the keys in the subspace and executes the 133 // provided callback. If the callback returns true for a given key, iteration 134 // will halt. 135 func (s Subspace) IterateKeys(ctx sdk.Context, cb func(key []byte) bool) { 136 store := s.kvStore(ctx) 137 138 iter := storetypes.KVStorePrefixIterator(store, nil) 139 defer iter.Close() 140 141 for ; iter.Valid(); iter.Next() { 142 if cb(iter.Key()) { 143 break 144 } 145 } 146 } 147 148 // GetRaw queries for the raw values bytes for a parameter by key. 149 func (s Subspace) GetRaw(ctx sdk.Context, key []byte) []byte { 150 store := s.kvStore(ctx) 151 return store.Get(key) 152 } 153 154 // Has returns if a parameter key exists or not in the Subspace's KVStore. 155 func (s Subspace) Has(ctx sdk.Context, key []byte) bool { 156 store := s.kvStore(ctx) 157 return store.Has(key) 158 } 159 160 // Modified returns true if the parameter key is set in the Subspace's transient 161 // KVStore. 162 func (s Subspace) Modified(ctx sdk.Context, key []byte) bool { 163 tstore := s.transientStore(ctx) 164 return tstore.Has(key) 165 } 166 167 // checkType verifies that the provided key and value are comptable and registered. 168 func (s Subspace) checkType(key []byte, value interface{}) { 169 attr, ok := s.table.m[string(key)] 170 if !ok { 171 panic(fmt.Sprintf("parameter %s not registered", key)) 172 } 173 174 ty := attr.ty 175 pty := reflect.TypeOf(value) 176 if pty.Kind() == reflect.Ptr { 177 pty = pty.Elem() 178 } 179 180 if pty != ty { 181 panic("type mismatch with registered table") 182 } 183 } 184 185 // Set stores a value for given a parameter key assuming the parameter type has 186 // been registered. It will panic if the parameter type has not been registered 187 // or if the value cannot be encoded. A change record is also set in the Subspace's 188 // transient KVStore to mark the parameter as modified. 189 func (s Subspace) Set(ctx sdk.Context, key []byte, value interface{}) { 190 s.checkType(key, value) 191 store := s.kvStore(ctx) 192 193 bz, err := s.legacyAmino.MarshalJSON(value) 194 if err != nil { 195 panic(err) 196 } 197 198 store.Set(key, bz) 199 200 tstore := s.transientStore(ctx) 201 tstore.Set(key, []byte{}) 202 } 203 204 // Update stores an updated raw value for a given parameter key assuming the 205 // parameter type has been registered. It will panic if the parameter type has 206 // not been registered or if the value cannot be encoded. An error is returned 207 // if the raw value is not compatible with the registered type for the parameter 208 // key or if the new value is invalid as determined by the registered type's 209 // validation function. 210 func (s Subspace) Update(ctx sdk.Context, key, value []byte) error { 211 attr, ok := s.table.m[string(key)] 212 if !ok { 213 panic(fmt.Sprintf("parameter %s not registered", key)) 214 } 215 216 ty := attr.ty 217 dest := reflect.New(ty).Interface() 218 s.GetIfExists(ctx, key, dest) 219 220 if err := s.legacyAmino.UnmarshalJSON(value, dest); err != nil { 221 return err 222 } 223 224 // destValue contains the dereferenced value of dest so validation function do 225 // not have to operate on pointers. 226 destValue := reflect.Indirect(reflect.ValueOf(dest)).Interface() 227 if err := s.Validate(ctx, key, destValue); err != nil { 228 return err 229 } 230 231 s.Set(ctx, key, dest) 232 return nil 233 } 234 235 // GetParamSet iterates through each ParamSetPair where for each pair, it will 236 // retrieve the value and set it to the corresponding value pointer provided 237 // in the ParamSetPair by calling Subspace#Get. 238 func (s Subspace) GetParamSet(ctx sdk.Context, ps ParamSet) { 239 for _, pair := range ps.ParamSetPairs() { 240 s.Get(ctx, pair.Key, pair.Value) 241 } 242 } 243 244 // GetParamSetIfExists iterates through each ParamSetPair where for each pair, it will 245 // retrieve the value and set it to the corresponding value pointer provided 246 // in the ParamSetPair by calling Subspace#GetIfExists. 247 func (s Subspace) GetParamSetIfExists(ctx sdk.Context, ps ParamSet) { 248 for _, pair := range ps.ParamSetPairs() { 249 s.GetIfExists(ctx, pair.Key, pair.Value) 250 } 251 } 252 253 // SetParamSet iterates through each ParamSetPair and sets the value with the 254 // corresponding parameter key in the Subspace's KVStore. 255 func (s Subspace) SetParamSet(ctx sdk.Context, ps ParamSet) { 256 for _, pair := range ps.ParamSetPairs() { 257 // pair.Field is a pointer to the field, so indirecting the ptr. 258 // go-amino automatically handles it but just for sure, 259 // since SetStruct is meant to be used in InitGenesis 260 // so this method will not be called frequently 261 v := reflect.Indirect(reflect.ValueOf(pair.Value)).Interface() 262 263 if err := pair.ValidatorFn(v); err != nil { 264 panic(fmt.Sprintf("value from ParamSetPair is invalid: %s", err)) 265 } 266 267 s.Set(ctx, pair.Key, v) 268 } 269 } 270 271 // Name returns the name of the Subspace. 272 func (s Subspace) Name() string { 273 return string(s.name) 274 } 275 276 // Wrapper of Subspace, provides immutable functions only 277 type ReadOnlySubspace struct { 278 s Subspace 279 } 280 281 // Get delegates a read-only Get call to the Subspace. 282 func (ros ReadOnlySubspace) Get(ctx sdk.Context, key []byte, ptr interface{}) { 283 ros.s.Get(ctx, key, ptr) 284 } 285 286 // GetRaw delegates a read-only GetRaw call to the Subspace. 287 func (ros ReadOnlySubspace) GetRaw(ctx sdk.Context, key []byte) []byte { 288 return ros.s.GetRaw(ctx, key) 289 } 290 291 // Has delegates a read-only Has call to the Subspace. 292 func (ros ReadOnlySubspace) Has(ctx sdk.Context, key []byte) bool { 293 return ros.s.Has(ctx, key) 294 } 295 296 // Modified delegates a read-only Modified call to the Subspace. 297 func (ros ReadOnlySubspace) Modified(ctx sdk.Context, key []byte) bool { 298 return ros.s.Modified(ctx, key) 299 } 300 301 // Name delegates a read-only Name call to the Subspace. 302 func (ros ReadOnlySubspace) Name() string { 303 return ros.s.Name() 304 }