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