-
Notifications
You must be signed in to change notification settings - Fork 3.6k
/
subspace.go
303 lines (251 loc) · 9.07 KB
/
subspace.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
package types
import (
"fmt"
"reflect"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
storetypes "github.com/cosmos/cosmos-sdk/store/types"
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.BinaryCodec
legacyAmino *codec.LegacyAmino
key storetypes.StoreKey // []byte -> []byte, stores parameter
tkey storetypes.StoreKey // []byte -> bool, stores parameter change
name []byte
table KeyTable
}
// NewSubspace constructs a store with namestore
func NewSubspace(cdc codec.BinaryCodec, legacyAmino *codec.LegacyAmino, key storetypes.StoreKey, tkey storetypes.StoreKey, name string) Subspace {
return Subspace{
cdc: cdc,
legacyAmino: legacyAmino,
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", 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.legacyAmino.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.legacyAmino.UnmarshalJSON(bz, ptr); err != nil {
panic(err)
}
}
// IterateKeys iterates over all the keys in the subspace and executes the
// provided callback. If the callback returns true for a given key, iteration
// will halt.
func (s Subspace) IterateKeys(ctx sdk.Context, cb func(key []byte) bool) {
store := s.kvStore(ctx)
iter := sdk.KVStorePrefixIterator(store, nil)
defer iter.Close()
for ; iter.Valid(); iter.Next() {
if cb(iter.Key()) {
break
}
}
}
// 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", 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.legacyAmino.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", key))
}
ty := attr.ty
dest := reflect.New(ty).Interface()
s.GetIfExists(ctx, key, dest)
if err := s.legacyAmino.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)
}
}
// GetParamSetIfExists 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#GetIfExists.
func (s Subspace) GetParamSetIfExists(ctx sdk.Context, ps ParamSet) {
for _, pair := range ps.ParamSetPairs() {
s.GetIfExists(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()
}