276 lines
8.1 KiB
Go
276 lines
8.1 KiB
Go
package types
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
|
|
"github.com/cosmos/cosmos-sdk/codec"
|
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
)
|
|
|
|
const (
|
|
// StoreKey is the string store key for the param store
|
|
StoreKey = "params"
|
|
|
|
// TStoreKey is the string store key for the param transient store
|
|
TStoreKey = "transient_params"
|
|
)
|
|
|
|
// Individual parameter store for each keeper
|
|
// Transient store persists for a block, so we use it for
|
|
// recording whether the parameter has been changed or not
|
|
type Subspace struct {
|
|
cdc codec.Marshaler
|
|
key sdk.StoreKey // []byte -> []byte, stores parameter
|
|
tkey sdk.StoreKey // []byte -> bool, stores parameter change
|
|
name []byte
|
|
table KeyTable
|
|
}
|
|
|
|
// NewSubspace constructs a store with namestore
|
|
func NewSubspace(cdc codec.Marshaler, key sdk.StoreKey, tkey sdk.StoreKey, name string) Subspace {
|
|
return Subspace{
|
|
cdc: cdc,
|
|
key: key,
|
|
tkey: tkey,
|
|
name: []byte(name),
|
|
table: NewKeyTable(),
|
|
}
|
|
}
|
|
|
|
// HasKeyTable returns if the Subspace has a KeyTable registered.
|
|
func (s Subspace) HasKeyTable() bool {
|
|
return len(s.table.m) > 0
|
|
}
|
|
|
|
// WithKeyTable initializes KeyTable and returns modified Subspace
|
|
func (s Subspace) WithKeyTable(table KeyTable) Subspace {
|
|
if table.m == nil {
|
|
panic("SetKeyTable() called with nil KeyTable")
|
|
}
|
|
if len(s.table.m) != 0 {
|
|
panic("SetKeyTable() called on already initialized Subspace")
|
|
}
|
|
|
|
for k, v := range table.m {
|
|
s.table.m[k] = v
|
|
}
|
|
|
|
// Allocate additional capacity for Subspace.name
|
|
// So we don't have to allocate extra space each time appending to the key
|
|
name := s.name
|
|
s.name = make([]byte, len(name), len(name)+table.maxKeyLength())
|
|
copy(s.name, name)
|
|
|
|
return s
|
|
}
|
|
|
|
// Returns a KVStore identical with ctx.KVStore(s.key).Prefix()
|
|
func (s Subspace) kvStore(ctx sdk.Context) sdk.KVStore {
|
|
// append here is safe, appends within a function won't cause
|
|
// weird side effects when its singlethreaded
|
|
return prefix.NewStore(ctx.KVStore(s.key), append(s.name, '/'))
|
|
}
|
|
|
|
// Returns a transient store for modification
|
|
func (s Subspace) transientStore(ctx sdk.Context) sdk.KVStore {
|
|
// append here is safe, appends within a function won't cause
|
|
// weird side effects when its singlethreaded
|
|
return prefix.NewStore(ctx.TransientStore(s.tkey), append(s.name, '/'))
|
|
}
|
|
|
|
// Validate attempts to validate a parameter value by its key. If the key is not
|
|
// registered or if the validation of the value fails, an error is returned.
|
|
func (s Subspace) Validate(ctx sdk.Context, key []byte, value interface{}) error {
|
|
attr, ok := s.table.m[string(key)]
|
|
if !ok {
|
|
return fmt.Errorf("parameter %s not registered", string(key))
|
|
}
|
|
|
|
if err := attr.vfn(value); err != nil {
|
|
return fmt.Errorf("invalid parameter value: %s", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Get queries for a parameter by key from the Subspace's KVStore and sets the
|
|
// value to the provided pointer. If the value does not exist, it will panic.
|
|
func (s Subspace) Get(ctx sdk.Context, key []byte, ptr interface{}) {
|
|
s.checkType(key, ptr)
|
|
|
|
store := s.kvStore(ctx)
|
|
bz := store.Get(key)
|
|
|
|
if err := s.cdc.UnmarshalJSON(bz, ptr); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// GetIfExists queries for a parameter by key from the Subspace's KVStore and
|
|
// sets the value to the provided pointer. If the value does not exist, it will
|
|
// perform a no-op.
|
|
func (s Subspace) GetIfExists(ctx sdk.Context, key []byte, ptr interface{}) {
|
|
store := s.kvStore(ctx)
|
|
bz := store.Get(key)
|
|
if bz == nil {
|
|
return
|
|
}
|
|
|
|
s.checkType(key, ptr)
|
|
|
|
if err := s.cdc.UnmarshalJSON(bz, ptr); err != nil {
|
|
panic(err)
|
|
}
|
|
}
|
|
|
|
// GetRaw queries for the raw values bytes for a parameter by key.
|
|
func (s Subspace) GetRaw(ctx sdk.Context, key []byte) []byte {
|
|
store := s.kvStore(ctx)
|
|
return store.Get(key)
|
|
}
|
|
|
|
// Has returns if a parameter key exists or not in the Subspace's KVStore.
|
|
func (s Subspace) Has(ctx sdk.Context, key []byte) bool {
|
|
store := s.kvStore(ctx)
|
|
return store.Has(key)
|
|
}
|
|
|
|
// Modified returns true if the parameter key is set in the Subspace's transient
|
|
// KVStore.
|
|
func (s Subspace) Modified(ctx sdk.Context, key []byte) bool {
|
|
tstore := s.transientStore(ctx)
|
|
return tstore.Has(key)
|
|
}
|
|
|
|
// checkType verifies that the provided key and value are comptable and registered.
|
|
func (s Subspace) checkType(key []byte, value interface{}) {
|
|
attr, ok := s.table.m[string(key)]
|
|
if !ok {
|
|
panic(fmt.Sprintf("parameter %s not registered", string(key)))
|
|
}
|
|
|
|
ty := attr.ty
|
|
pty := reflect.TypeOf(value)
|
|
if pty.Kind() == reflect.Ptr {
|
|
pty = pty.Elem()
|
|
}
|
|
|
|
if pty != ty {
|
|
panic("type mismatch with registered table")
|
|
}
|
|
}
|
|
|
|
// Set stores a value for given a parameter key assuming the parameter type has
|
|
// been registered. It will panic if the parameter type has not been registered
|
|
// or if the value cannot be encoded. A change record is also set in the Subspace's
|
|
// transient KVStore to mark the parameter as modified.
|
|
func (s Subspace) Set(ctx sdk.Context, key []byte, value interface{}) {
|
|
s.checkType(key, value)
|
|
store := s.kvStore(ctx)
|
|
|
|
bz, err := s.cdc.MarshalJSON(value)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
store.Set(key, bz)
|
|
|
|
tstore := s.transientStore(ctx)
|
|
tstore.Set(key, []byte{})
|
|
}
|
|
|
|
// Update stores an updated raw value for a given parameter key assuming the
|
|
// parameter type has been registered. It will panic if the parameter type has
|
|
// not been registered or if the value cannot be encoded. An error is returned
|
|
// if the raw value is not compatible with the registered type for the parameter
|
|
// key or if the new value is invalid as determined by the registered type's
|
|
// validation function.
|
|
func (s Subspace) Update(ctx sdk.Context, key, value []byte) error {
|
|
attr, ok := s.table.m[string(key)]
|
|
if !ok {
|
|
panic(fmt.Sprintf("parameter %s not registered", string(key)))
|
|
}
|
|
|
|
ty := attr.ty
|
|
dest := reflect.New(ty).Interface()
|
|
s.GetIfExists(ctx, key, dest)
|
|
|
|
if err := s.cdc.UnmarshalJSON(value, dest); err != nil {
|
|
return err
|
|
}
|
|
|
|
// destValue contains the dereferenced value of dest so validation function do
|
|
// not have to operate on pointers.
|
|
destValue := reflect.Indirect(reflect.ValueOf(dest)).Interface()
|
|
if err := s.Validate(ctx, key, destValue); err != nil {
|
|
return err
|
|
}
|
|
|
|
s.Set(ctx, key, dest)
|
|
return nil
|
|
}
|
|
|
|
// GetParamSet iterates through each ParamSetPair where for each pair, it will
|
|
// retrieve the value and set it to the corresponding value pointer provided
|
|
// in the ParamSetPair by calling Subspace#Get.
|
|
func (s Subspace) GetParamSet(ctx sdk.Context, ps ParamSet) {
|
|
for _, pair := range ps.ParamSetPairs() {
|
|
s.Get(ctx, pair.Key, pair.Value)
|
|
}
|
|
}
|
|
|
|
// SetParamSet iterates through each ParamSetPair and sets the value with the
|
|
// corresponding parameter key in the Subspace's KVStore.
|
|
func (s Subspace) SetParamSet(ctx sdk.Context, ps ParamSet) {
|
|
for _, pair := range ps.ParamSetPairs() {
|
|
// pair.Field is a pointer to the field, so indirecting the ptr.
|
|
// go-amino automatically handles it but just for sure,
|
|
// since SetStruct is meant to be used in InitGenesis
|
|
// so this method will not be called frequently
|
|
v := reflect.Indirect(reflect.ValueOf(pair.Value)).Interface()
|
|
|
|
if err := pair.ValidatorFn(v); err != nil {
|
|
panic(fmt.Sprintf("value from ParamSetPair is invalid: %s", err))
|
|
}
|
|
|
|
s.Set(ctx, pair.Key, v)
|
|
}
|
|
}
|
|
|
|
// Name returns the name of the Subspace.
|
|
func (s Subspace) Name() string {
|
|
return string(s.name)
|
|
}
|
|
|
|
// Wrapper of Subspace, provides immutable functions only
|
|
type ReadOnlySubspace struct {
|
|
s Subspace
|
|
}
|
|
|
|
// Get delegates a read-only Get call to the Subspace.
|
|
func (ros ReadOnlySubspace) Get(ctx sdk.Context, key []byte, ptr interface{}) {
|
|
ros.s.Get(ctx, key, ptr)
|
|
}
|
|
|
|
// GetRaw delegates a read-only GetRaw call to the Subspace.
|
|
func (ros ReadOnlySubspace) GetRaw(ctx sdk.Context, key []byte) []byte {
|
|
return ros.s.GetRaw(ctx, key)
|
|
}
|
|
|
|
// Has delegates a read-only Has call to the Subspace.
|
|
func (ros ReadOnlySubspace) Has(ctx sdk.Context, key []byte) bool {
|
|
return ros.s.Has(ctx, key)
|
|
}
|
|
|
|
// Modified delegates a read-only Modified call to the Subspace.
|
|
func (ros ReadOnlySubspace) Modified(ctx sdk.Context, key []byte) bool {
|
|
return ros.s.Modified(ctx, key)
|
|
}
|
|
|
|
// Name delegates a read-only Name call to the Subspace.
|
|
func (ros ReadOnlySubspace) Name() string {
|
|
return ros.s.Name()
|
|
}
|