github.com/fibonacci-chain/fbc@v0.0.0-20231124064014-c7636198c1e9/libs/cosmos-sdk/x/params/subspace/subspace.go (about) 1 package subspace 2 3 import ( 4 "bytes" 5 "fmt" 6 "reflect" 7 "strconv" 8 9 "github.com/valyala/fastjson" 10 11 "encoding/json" 12 13 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/codec" 14 sdk "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/types" 15 "github.com/tendermint/go-amino" 16 17 "github.com/fibonacci-chain/fbc/libs/cosmos-sdk/store/prefix" 18 ) 19 20 const ( 21 // StoreKey is the string store key for the param store 22 StoreKey = "params" 23 24 // TStoreKey is the string store key for the param transient store 25 TStoreKey = "transient_params" 26 ) 27 28 // Individual parameter store for each keeper 29 // Transient store persists for a block, so we use it for 30 // recording whether the parameter has been changed or not 31 type Subspace struct { 32 cdc *codec.Codec 33 key sdk.StoreKey // []byte -> []byte, stores parameter 34 tkey sdk.StoreKey // []byte -> bool, stores parameter change 35 name []byte 36 cName []byte 37 table KeyTable 38 } 39 40 // NewSubspace constructs a store with namestore 41 func NewSubspace(cdc *codec.Codec, key sdk.StoreKey, tkey sdk.StoreKey, name string) Subspace { 42 return Subspace{ 43 cdc: cdc, 44 key: key, 45 tkey: tkey, 46 name: []byte(name), 47 cName: []byte("custom/" + name + "/"), 48 table: NewKeyTable(), 49 } 50 } 51 52 // HasKeyTable returns if the Subspace has a KeyTable registered. 53 func (s Subspace) HasKeyTable() bool { 54 return len(s.table.m) > 0 55 } 56 57 // WithKeyTable initializes KeyTable and returns modified Subspace 58 func (s Subspace) WithKeyTable(table KeyTable) Subspace { 59 if table.m == nil { 60 panic("SetKeyTable() called with nil KeyTable") 61 } 62 if len(s.table.m) != 0 { 63 panic("SetKeyTable() called on already initialized Subspace") 64 } 65 if s.table.m == nil { 66 s.table.m = make(map[string]attribute) 67 } 68 69 for k, v := range table.m { 70 s.table.m[k] = v 71 } 72 73 // Allocate additional capacity for Subspace.name 74 // So we don't have to allocate extra space each time appending to the key 75 name := s.name 76 s.name = make([]byte, len(name), len(name)+table.maxKeyLength()) 77 copy(s.name, name) 78 79 return s 80 } 81 82 func (s Subspace) LazyWithKeyTable(table KeyTable) Subspace { 83 if table.m == nil { 84 panic("SetKeyTable() called with nil KeyTable") 85 } 86 if len(s.table.m) == 0 { 87 panic("SetKeyTable() should call on already initialized Subspace") 88 } 89 90 for k, v := range table.m { 91 s.table.m[k] = v 92 } 93 94 // Allocate additional capacity for Subspace.name 95 // So we don't have to allocate extra space each time appending to the key 96 name := s.name 97 s.name = make([]byte, len(name), len(name)+table.maxKeyLength()) 98 copy(s.name, name) 99 100 return s 101 } 102 103 // Returns a KVStore identical with ctx.KVStore(s.key).Prefix() 104 func (s Subspace) kvStore(ctx sdk.Context) sdk.KVStore { 105 // append here is safe, appends within a function won't cause 106 // weird side effects when its singlethreaded 107 return prefix.NewStore(ctx.KVStore(s.key), append(s.name, '/')) 108 } 109 110 // Returns a KVStore identical with ctx.KVStore(s.key).Prefix() 111 func (s Subspace) CustomKVStore(ctx sdk.Context) sdk.KVStore { 112 // append here is safe, appends within a function won't cause 113 // weird side effects when its singlethreaded 114 return prefix.NewStore(ctx.KVStore(s.key), s.cName) 115 } 116 117 // Returns a transient store for modification 118 func (s Subspace) transientStore(ctx sdk.Context) sdk.KVStore { 119 // append here is safe, appends within a function won't cause 120 // weird side effects when its singlethreaded 121 return prefix.NewStore(ctx.TransientStore(s.tkey), append(s.name, '/')) 122 } 123 124 // Validate attempts to validate a parameter value by its key. If the key is not 125 // registered or if the validation of the value fails, an error is returned. 126 func (s Subspace) Validate(ctx sdk.Context, key []byte, value interface{}) error { 127 attr, ok := s.table.m[string(key)] 128 if !ok { 129 return fmt.Errorf("parameter %s not registered", string(key)) 130 } 131 132 if err := attr.vfn(value); err != nil { 133 return fmt.Errorf("invalid parameter value: %s", err) 134 } 135 136 return nil 137 } 138 139 // Get queries for a parameter by key from the Subspace's KVStore and sets the 140 // value to the provided pointer. If the value does not exist, it will panic. 141 func (s Subspace) Get(ctx sdk.Context, key []byte, ptr interface{}) { 142 store := s.kvStore(ctx) 143 bz := store.Get(key) 144 145 if err := tryParseJSON(bz, ptr, s.cdc); err != nil { 146 panic(err) 147 } 148 } 149 150 func isJsonString(bz []byte) bool { 151 return len(bz) >= 2 && bz[0] == '"' && bz[len(bz)-1] == '"' 152 } 153 154 func getString(rawbz []byte, tptr *string) bool { 155 ok := true 156 for _, c := range rawbz { 157 if c < 0x20 || c == '\\' { 158 ok = false 159 break 160 } 161 } 162 if !ok { 163 return false 164 } 165 *tptr = string(rawbz) 166 return true 167 } 168 169 func getUint64(rawbz []byte, tptr *uint64) bool { 170 ok := true 171 for _, c := range rawbz { 172 if c < '0' || c > '9' { 173 ok = false 174 break 175 } 176 } 177 if !ok { 178 return false 179 } 180 ui, err := strconv.ParseUint(amino.BytesToStr(rawbz), 10, 64) 181 if err == nil { 182 *tptr = ui 183 return true 184 } 185 return false 186 } 187 188 func getInt64(rawbz []byte, tptr *int64) bool { 189 ok := true 190 rawbz2 := rawbz 191 if rawbz2[0] == '-' { 192 rawbz2 = rawbz[1:] 193 } 194 for _, c := range rawbz2 { 195 if c < '0' || c > '9' { 196 ok = false 197 break 198 } 199 } 200 if !ok { 201 return false 202 } 203 ui, err := strconv.ParseInt(amino.BytesToStr(rawbz), 10, 64) 204 if err == nil { 205 *tptr = ui 206 return true 207 } 208 return false 209 } 210 211 func getDec(rawbz []byte, tptr *sdk.Dec) bool { 212 ok := true 213 for _, c := range rawbz { 214 if c < 0x20 || c == '\\' { 215 ok = false 216 break 217 } 218 } 219 if !ok { 220 return false 221 } 222 nd, err := sdk.NewDecFromStr(amino.BytesToStr(rawbz)) 223 if err == nil { 224 tptr.Int = nd.Int 225 return true 226 } 227 return false 228 } 229 230 func getDecCoin(bz []byte, tptr *sdk.DecCoin) bool { 231 if amino.BytesToStr(bz) == "null" { 232 return false 233 } 234 v, err := fastjson.Parse(amino.BytesToStr(bz)) 235 if err == nil { 236 tptr.Denom = string(v.GetStringBytes("denom")) 237 amount := v.GetStringBytes("amount") 238 newDec, err := sdk.NewDecFromStr(amino.BytesToStr(amount)) 239 if err == nil { 240 tptr.Amount.Int = newDec.Int 241 return true 242 } 243 } 244 return false 245 } 246 247 func tryParseJSON(bz []byte, ptr interface{}, cdc *amino.Codec) error { 248 if len(bz) < 2 { 249 return cdc.UnmarshalJSON(bz, ptr) 250 } 251 if isJsonString(bz) { 252 rawbz := bz[1 : len(bz)-1] // trim leading & trailing " 253 switch tptr := ptr.(type) { 254 case *string: 255 if getString(rawbz, tptr) { 256 return nil 257 } 258 return json.Unmarshal(bz, ptr) 259 case *uint64: 260 if getUint64(rawbz, tptr) { 261 return nil 262 } 263 return json.Unmarshal(rawbz, ptr) 264 case *int64: 265 if getInt64(rawbz, tptr) { 266 return nil 267 } 268 return json.Unmarshal(rawbz, ptr) 269 case *sdk.Dec: 270 if getDec(rawbz, tptr) { 271 return nil 272 } 273 return tptr.UnmarshalJSON(bz) 274 } 275 } else { 276 switch tptr := ptr.(type) { 277 case *sdk.DecCoin: 278 if getDecCoin(bz, tptr) { 279 return nil 280 } 281 case *bool: 282 switch amino.BytesToStr(bz) { 283 case "true": 284 *tptr = true 285 return nil 286 case "false": 287 *tptr = false 288 return nil 289 } 290 case *[]int: 291 if amino.BytesToStr(bz) == "null" { 292 *tptr = nil 293 return nil 294 } 295 } 296 } 297 298 return cdc.UnmarshalJSON(bz, ptr) 299 } 300 301 // GetIfExists queries for a parameter by key from the Subspace's KVStore and 302 // sets the value to the provided pointer. If the value does not exist, it will 303 // perform a no-op. 304 func (s Subspace) GetIfExists(ctx sdk.Context, key []byte, ptr interface{}) { 305 store := s.kvStore(ctx) 306 bz := store.Get(key) 307 if bz == nil { 308 return 309 } 310 311 if err := s.cdc.UnmarshalJSON(bz, ptr); err != nil { 312 panic(err) 313 } 314 } 315 316 // GetRaw queries for the raw values bytes for a parameter by key. 317 func (s Subspace) GetRaw(ctx sdk.Context, key []byte) []byte { 318 store := s.kvStore(ctx) 319 return store.Get(key) 320 } 321 322 // Has returns if a parameter key exists or not in the Subspace's KVStore. 323 func (s Subspace) Has(ctx sdk.Context, key []byte) bool { 324 store := s.kvStore(ctx) 325 return store.Has(key) 326 } 327 328 // Modified returns true if the parameter key is set in the Subspace's transient 329 // KVStore. 330 func (s Subspace) Modified(ctx sdk.Context, key []byte) bool { 331 tstore := s.transientStore(ctx) 332 return tstore.Has(key) 333 } 334 335 // checkType verifies that the provided key and value are comptable and registered. 336 func (s Subspace) checkType(key []byte, value interface{}) { 337 attr, ok := s.table.m[string(key)] 338 if !ok { 339 panic(fmt.Sprintf("parameter %s not registered", string(key))) 340 } 341 342 ty := attr.ty 343 pty := reflect.TypeOf(value) 344 if pty.Kind() == reflect.Ptr { 345 pty = pty.Elem() 346 } 347 348 if pty != ty { 349 panic("type mismatch with registered table") 350 } 351 } 352 353 // Set stores a value for given a parameter key assuming the parameter type has 354 // been registered. It will panic if the parameter type has not been registered 355 // or if the value cannot be encoded. A change record is also set in the Subspace's 356 // transient KVStore to mark the parameter as modified. 357 func (s Subspace) Set(ctx sdk.Context, key []byte, value interface{}) { 358 s.checkType(key, value) 359 store := s.kvStore(ctx) 360 361 bz, err := s.cdc.MarshalJSON(value) 362 if err != nil { 363 panic(err) 364 } 365 366 store.Set(key, bz) 367 368 tstore := s.transientStore(ctx) 369 tstore.Set(key, []byte{}) 370 } 371 372 // Update stores an updated raw value for a given parameter key assuming the 373 // parameter type has been registered. It will panic if the parameter type has 374 // not been registered or if the value cannot be encoded. An error is returned 375 // if the raw value is not compatible with the registered type for the parameter 376 // key or if the new value is invalid as determined by the registered type's 377 // validation function. 378 func (s Subspace) Update(ctx sdk.Context, key, value []byte) error { 379 attr, ok := s.table.m[string(key)] 380 if !ok { 381 panic(fmt.Sprintf("parameter %s not registered", string(key))) 382 } 383 384 ty := attr.ty 385 dest := reflect.New(ty).Interface() 386 s.GetIfExists(ctx, key, dest) 387 388 if err := s.cdc.UnmarshalJSON(value, dest); err != nil { 389 return err 390 } 391 392 // destValue contains the dereferenced value of dest so validation function do 393 // not have to operate on pointers. 394 destValue := reflect.Indirect(reflect.ValueOf(dest)).Interface() 395 if err := s.Validate(ctx, key, destValue); err != nil { 396 return err 397 } 398 399 s.Set(ctx, key, dest) 400 return nil 401 } 402 403 // GetParamSet iterates through each ParamSetPair where for each pair, it will 404 // retrieve the value and set it to the corresponding value pointer provided 405 // in the ParamSetPair by calling Subspace#Get. 406 func (s Subspace) GetParamSet(ctx sdk.Context, ps ParamSet) { 407 for _, pair := range ps.ParamSetPairs() { 408 s.Get(ctx, pair.Key, pair.Value) 409 } 410 } 411 412 // GetParamSetForInitGenesis iterates through each ParamSetPair where for each pair, it will 413 // retrieve the value and set it to the corresponding value pointer provided, ignore the target keys for additional 414 // in the ParamSetPair by calling Subspace#Get. 415 func (s Subspace) GetParamSetForInitGenesis(ctx sdk.Context, ps ParamSet, ignoreList [][]byte) { 416 for _, pair := range ps.ParamSetPairs() { 417 beIgnore := false 418 for _, ignore := range ignoreList { 419 if bytes.Equal(ignore, pair.Key) { 420 beIgnore = true 421 break 422 } 423 } 424 425 if beIgnore { 426 continue 427 } 428 s.Get(ctx, pair.Key, pair.Value) 429 } 430 } 431 432 // SetParamSet iterates through each ParamSetPair and sets the value with the 433 // corresponding parameter key in the Subspace's KVStore. 434 func (s Subspace) SetParamSet(ctx sdk.Context, ps ParamSet) { 435 for _, pair := range ps.ParamSetPairs() { 436 // pair.Field is a pointer to the field, so indirecting the ptr. 437 // go-amino automatically handles it but just for sure, 438 // since SetStruct is meant to be used in InitGenesis 439 // so this method will not be called frequently 440 v := reflect.Indirect(reflect.ValueOf(pair.Value)).Interface() 441 442 if err := pair.ValidatorFn(v); err != nil { 443 panic(fmt.Sprintf("value from ParamSetPair is invalid: %s", err)) 444 } 445 446 s.Set(ctx, pair.Key, v) 447 } 448 } 449 450 // SetParamSetForInitGenesis iterates through each ParamSetPair and sets the value with the 451 // corresponding parameter key in the Subspace's KVStore, ignore the target keys for additional 452 func (s Subspace) SetParamSetForInitGenesis(ctx sdk.Context, ps ParamSet, ignoreList [][]byte) { 453 for _, pair := range ps.ParamSetPairs() { 454 beIgnore := false 455 for _, ignore := range ignoreList { 456 if bytes.Equal(ignore, pair.Key) { 457 beIgnore = true 458 break 459 } 460 } 461 462 if beIgnore { 463 continue 464 } 465 466 // pair.Field is a pointer to the field, so indirecting the ptr. 467 // go-amino automatically handles it but just for sure, 468 // since SetStruct is meant to be used in InitGenesis 469 // so this method will not be called frequently 470 v := reflect.Indirect(reflect.ValueOf(pair.Value)).Interface() 471 472 if err := pair.ValidatorFn(v); err != nil { 473 panic(fmt.Sprintf("value from ParamSetPair is invalid: %s", err)) 474 } 475 476 s.Set(ctx, pair.Key, v) 477 } 478 } 479 480 // Name returns the name of the Subspace. 481 func (s Subspace) Name() string { 482 return string(s.name) 483 } 484 485 // Wrapper of Subspace, provides immutable functions only 486 type ReadOnlySubspace struct { 487 s Subspace 488 } 489 490 // Get delegates a read-only Get call to the Subspace. 491 func (ros ReadOnlySubspace) Get(ctx sdk.Context, key []byte, ptr interface{}) { 492 ros.s.Get(ctx, key, ptr) 493 } 494 495 // GetRaw delegates a read-only GetRaw call to the Subspace. 496 func (ros ReadOnlySubspace) GetRaw(ctx sdk.Context, key []byte) []byte { 497 return ros.s.GetRaw(ctx, key) 498 } 499 500 // Has delegates a read-only Has call to the Subspace. 501 func (ros ReadOnlySubspace) Has(ctx sdk.Context, key []byte) bool { 502 return ros.s.Has(ctx, key) 503 } 504 505 // Modified delegates a read-only Modified call to the Subspace. 506 func (ros ReadOnlySubspace) Modified(ctx sdk.Context, key []byte) bool { 507 return ros.s.Modified(ctx, key) 508 } 509 510 // Name delegates a read-only Name call to the Subspace. 511 func (ros ReadOnlySubspace) Name() string { 512 return ros.s.Name() 513 }