-
Notifications
You must be signed in to change notification settings - Fork 0
/
subspace.go
513 lines (444 loc) · 13.3 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
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
package subspace
import (
"bytes"
"fmt"
"reflect"
"strconv"
"github.com/valyala/fastjson"
"encoding/json"
"github.com/evoblockchain/evochain/libs/cosmos-sdk/codec"
sdk "github.com/evoblockchain/evochain/libs/cosmos-sdk/types"
"github.com/tendermint/go-amino"
"github.com/evoblockchain/evochain/libs/cosmos-sdk/store/prefix"
)
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.Codec
key sdk.StoreKey // []byte -> []byte, stores parameter
tkey sdk.StoreKey // []byte -> bool, stores parameter change
name []byte
cName []byte
table KeyTable
}
// NewSubspace constructs a store with namestore
func NewSubspace(cdc *codec.Codec, key sdk.StoreKey, tkey sdk.StoreKey, name string) Subspace {
return Subspace{
cdc: cdc,
key: key,
tkey: tkey,
name: []byte(name),
cName: []byte("custom/" + 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")
}
if s.table.m == nil {
s.table.m = make(map[string]attribute)
}
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
}
func (s Subspace) LazyWithKeyTable(table KeyTable) Subspace {
if table.m == nil {
panic("SetKeyTable() called with nil KeyTable")
}
if len(s.table.m) == 0 {
panic("SetKeyTable() should call 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 KVStore identical with ctx.KVStore(s.key).Prefix()
func (s Subspace) CustomKVStore(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), s.cName)
}
// 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{}) {
store := s.kvStore(ctx)
bz := store.Get(key)
if err := tryParseJSON(bz, ptr, s.cdc); err != nil {
panic(err)
}
}
func isJsonString(bz []byte) bool {
return len(bz) >= 2 && bz[0] == '"' && bz[len(bz)-1] == '"'
}
func getString(rawbz []byte, tptr *string) bool {
ok := true
for _, c := range rawbz {
if c < 0x20 || c == '\\' {
ok = false
break
}
}
if !ok {
return false
}
*tptr = string(rawbz)
return true
}
func getUint64(rawbz []byte, tptr *uint64) bool {
ok := true
for _, c := range rawbz {
if c < '0' || c > '9' {
ok = false
break
}
}
if !ok {
return false
}
ui, err := strconv.ParseUint(amino.BytesToStr(rawbz), 10, 64)
if err == nil {
*tptr = ui
return true
}
return false
}
func getInt64(rawbz []byte, tptr *int64) bool {
ok := true
rawbz2 := rawbz
if rawbz2[0] == '-' {
rawbz2 = rawbz[1:]
}
for _, c := range rawbz2 {
if c < '0' || c > '9' {
ok = false
break
}
}
if !ok {
return false
}
ui, err := strconv.ParseInt(amino.BytesToStr(rawbz), 10, 64)
if err == nil {
*tptr = ui
return true
}
return false
}
func getDec(rawbz []byte, tptr *sdk.Dec) bool {
ok := true
for _, c := range rawbz {
if c < 0x20 || c == '\\' {
ok = false
break
}
}
if !ok {
return false
}
nd, err := sdk.NewDecFromStr(amino.BytesToStr(rawbz))
if err == nil {
tptr.Int = nd.Int
return true
}
return false
}
func getDecCoin(bz []byte, tptr *sdk.DecCoin) bool {
if amino.BytesToStr(bz) == "null" {
return false
}
v, err := fastjson.Parse(amino.BytesToStr(bz))
if err == nil {
tptr.Denom = string(v.GetStringBytes("denom"))
amount := v.GetStringBytes("amount")
newDec, err := sdk.NewDecFromStr(amino.BytesToStr(amount))
if err == nil {
tptr.Amount.Int = newDec.Int
return true
}
}
return false
}
func tryParseJSON(bz []byte, ptr interface{}, cdc *amino.Codec) error {
if len(bz) < 2 {
return cdc.UnmarshalJSON(bz, ptr)
}
if isJsonString(bz) {
rawbz := bz[1 : len(bz)-1] // trim leading & trailing "
switch tptr := ptr.(type) {
case *string:
if getString(rawbz, tptr) {
return nil
}
return json.Unmarshal(bz, ptr)
case *uint64:
if getUint64(rawbz, tptr) {
return nil
}
return json.Unmarshal(rawbz, ptr)
case *int64:
if getInt64(rawbz, tptr) {
return nil
}
return json.Unmarshal(rawbz, ptr)
case *sdk.Dec:
if getDec(rawbz, tptr) {
return nil
}
return tptr.UnmarshalJSON(bz)
}
} else {
switch tptr := ptr.(type) {
case *sdk.DecCoin:
if getDecCoin(bz, tptr) {
return nil
}
case *bool:
switch amino.BytesToStr(bz) {
case "true":
*tptr = true
return nil
case "false":
*tptr = false
return nil
}
case *[]int:
if amino.BytesToStr(bz) == "null" {
*tptr = nil
return nil
}
}
}
return cdc.UnmarshalJSON(bz, ptr)
}
// 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
}
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)
}
}
// GetParamSetForInitGenesis iterates through each ParamSetPair where for each pair, it will
// retrieve the value and set it to the corresponding value pointer provided, ignore the target keys for additional
// in the ParamSetPair by calling Subspace#Get.
func (s Subspace) GetParamSetForInitGenesis(ctx sdk.Context, ps ParamSet, ignoreList [][]byte) {
for _, pair := range ps.ParamSetPairs() {
beIgnore := false
for _, ignore := range ignoreList {
if bytes.Equal(ignore, pair.Key) {
beIgnore = true
break
}
}
if beIgnore {
continue
}
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)
}
}
// SetParamSetForInitGenesis iterates through each ParamSetPair and sets the value with the
// corresponding parameter key in the Subspace's KVStore, ignore the target keys for additional
func (s Subspace) SetParamSetForInitGenesis(ctx sdk.Context, ps ParamSet, ignoreList [][]byte) {
for _, pair := range ps.ParamSetPairs() {
beIgnore := false
for _, ignore := range ignoreList {
if bytes.Equal(ignore, pair.Key) {
beIgnore = true
break
}
}
if beIgnore {
continue
}
// 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()
}