Skip to content

Commit 10a6bc1

Browse files
authored
add kvstore (#1004)
* add kvstore * use a fork of substrate rpc client * fix lint * use client decode function * move decodeSecondKey to utils.go
1 parent ea6fdf3 commit 10a6bc1

File tree

7 files changed

+249
-586
lines changed

7 files changed

+249
-586
lines changed

clients/tfchain-client-go/account.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ type AccountID types.AccountID
3939
// Balance
4040
type Balance struct {
4141
Free types.U128 `json:"free"`
42-
Reserved types.U128 `json:"reserverd"`
42+
Reserved types.U128 `json:"reserved"`
4343
MiscFrozen types.U128 `json:"misc_frozen"`
4444
FreeFrozen types.U128 `json:"free_frozen"`
4545
}
@@ -54,7 +54,7 @@ type AccountInfo struct {
5454

5555
// TODO: Add service response status code if avialble
5656
type ActivationServiceError struct {
57-
StatusCode int // 0 or a valid HTTP status code. 0 indicates that a response was not recived due to an error such host unreachable.
57+
StatusCode int // 0 or a valid HTTP status code. 0 indicates that a response was not received due to an error such host unreachable.
5858
Err error
5959
}
6060

clients/tfchain-client-go/go.mod

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
module github.com/threefoldtech/tfchain/clients/tfchain-client-go
22

3-
go 1.17
3+
go 1.21
44

55
require (
66
github.com/cenkalti/backoff v2.2.1+incompatible
77
github.com/centrifuge/go-substrate-rpc-client/v4 v4.0.12
88
github.com/jbenet/go-base58 v0.0.0-20150317085156-6237cf65f3a6
99
github.com/pkg/errors v0.9.1
1010
github.com/rs/zerolog v1.26.0
11-
github.com/stretchr/testify v1.7.0
11+
github.com/stretchr/testify v1.7.2
1212
github.com/vedhavyas/go-subkey v1.0.3
13-
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871
13+
golang.org/x/crypto v0.7.0
1414
)
1515

1616
require (
@@ -19,20 +19,24 @@ require (
1919
github.com/cosmos/go-bip39 v1.0.0 // indirect
2020
github.com/davecgh/go-spew v1.1.1 // indirect
2121
github.com/deckarep/golang-set v1.8.0 // indirect
22-
github.com/decred/base58 v1.0.3 // indirect
22+
github.com/decred/base58 v1.0.4 // indirect
2323
github.com/decred/dcrd/crypto/blake256 v1.0.0 // indirect
24-
github.com/ethereum/go-ethereum v1.10.17 // indirect
24+
github.com/ethereum/go-ethereum v1.10.20 // indirect
2525
github.com/go-ole/go-ole v1.2.6 // indirect
2626
github.com/go-stack/stack v1.8.1 // indirect
2727
github.com/gorilla/websocket v1.5.0 // indirect
2828
github.com/gtank/merlin v0.1.1 // indirect
2929
github.com/gtank/ristretto255 v0.1.2 // indirect
30-
github.com/mimoo/StrobeGo v0.0.0-20210601165009-122bf33a46e0 // indirect
30+
github.com/kr/pretty v0.3.1 // indirect
31+
github.com/mimoo/StrobeGo v0.0.0-20220103164710-9a04d6ca976b // indirect
3132
github.com/pierrec/xxHash v0.1.5 // indirect
3233
github.com/pmezard/go-difflib v1.0.0 // indirect
3334
github.com/rs/cors v1.8.2 // indirect
3435
github.com/tklauser/go-sysconf v0.3.9 // indirect
35-
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 // indirect
36+
github.com/vedhavyas/go-subkey/v2 v2.0.0 // indirect
37+
golang.org/x/sys v0.6.0 // indirect
3638
gopkg.in/natefinch/npipe.v2 v2.0.0-20160621034901-c1b8fa8bdcce // indirect
37-
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
39+
gopkg.in/yaml.v3 v3.0.1 // indirect
3840
)
41+
42+
replace github.com/centrifuge/go-substrate-rpc-client/v4 => github.com/threefoldtech/go-substrate-rpc-client/v4 v4.0.0-20240916115924-b6e9bfa88b8a

clients/tfchain-client-go/go.sum

Lines changed: 25 additions & 570 deletions
Large diffs are not rendered by default.

clients/tfchain-client-go/kvstore.go

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
package substrate
2+
3+
import (
4+
"github.com/centrifuge/go-substrate-rpc-client/v4/types"
5+
6+
"github.com/pkg/errors"
7+
)
8+
9+
func (s *Substrate) KVStoreSet(identity Identity, key string, value string) error {
10+
cl, meta, err := s.GetClient()
11+
if err != nil {
12+
return err
13+
}
14+
15+
c, err := types.NewCall(meta, "TFKVStore.set",
16+
key, value,
17+
)
18+
if err != nil {
19+
return errors.Wrap(err, "failed to create call")
20+
}
21+
22+
res, err := s.Call(cl, meta, identity, c)
23+
if err != nil {
24+
return errors.Wrap(err, "failed to create contract")
25+
}
26+
27+
if err := s.checkForError(res); err != nil {
28+
return err
29+
}
30+
31+
return nil
32+
}
33+
34+
func (s *Substrate) KVStoreDelete(identity Identity, key string) error {
35+
cl, meta, err := s.GetClient()
36+
if err != nil {
37+
return err
38+
}
39+
40+
c, err := types.NewCall(meta, "TFKVStore.delete",
41+
key,
42+
)
43+
if err != nil {
44+
return errors.Wrap(err, "failed to create call")
45+
}
46+
47+
res, err := s.Call(cl, meta, identity, c)
48+
if err != nil {
49+
return errors.Wrap(err, "failed to create contract")
50+
}
51+
52+
if err := s.checkForError(res); err != nil {
53+
return err
54+
}
55+
return nil
56+
}
57+
58+
func (s *Substrate) KVStoreGet(identity Identity, key string) ([]byte, error) {
59+
cl, meta, err := s.GetClient()
60+
if err != nil {
61+
return nil, err
62+
}
63+
64+
bytes, err := Encode(key)
65+
if err != nil {
66+
return nil, err
67+
}
68+
69+
storageKey, err := types.CreateStorageKey(meta, "TFKVStore", "TFKVStore", identity.PublicKey(), bytes)
70+
if err != nil {
71+
return nil, errors.Wrap(err, "failed to create substrate query key")
72+
}
73+
74+
var value []byte
75+
ok, err := cl.RPC.State.GetStorageLatest(storageKey, &value)
76+
if err != nil {
77+
return nil, errors.Wrap(err, "failed to lookup entity")
78+
}
79+
80+
if !ok {
81+
return nil, errors.Wrap(ErrNotFound, "key not found")
82+
}
83+
84+
return value, nil
85+
}
86+
87+
type Val struct {
88+
Id string
89+
Key string
90+
}
91+
92+
func (s *Substrate) KVStoreList(identity Identity) (map[string]string, error) {
93+
cl, meta, err := s.GetClient()
94+
if err != nil {
95+
return nil, err
96+
}
97+
98+
storageKey, err := types.CreateStorageKey(meta, "TFKVStore", "TFKVStore", identity.PublicKey())
99+
if err != nil {
100+
return nil, errors.Wrap(err, "failed to create substrate query key")
101+
}
102+
103+
keys, err := cl.RPC.State.GetKeysLatest(storageKey)
104+
if err != nil {
105+
return nil, errors.Wrap(err, "failed to lookup entity")
106+
}
107+
108+
query, err := cl.RPC.State.QueryStorageAtLatest(keys)
109+
if err != nil {
110+
return nil, err
111+
}
112+
113+
pairs := make(map[string]string)
114+
for _, q := range query {
115+
for _, c := range q.Changes {
116+
var key, val []byte
117+
118+
key, err = decodeSecondKey(c.StorageKey, identity)
119+
if err != nil {
120+
return nil, err
121+
}
122+
123+
err = Decode(c.StorageData, &val)
124+
if err != nil {
125+
return nil, errors.Wrapf(err, "failed to decode value %v", string(c.StorageData))
126+
}
127+
128+
pairs[string(key)] = string(val)
129+
}
130+
}
131+
132+
return pairs, nil
133+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package substrate
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
"github.com/stretchr/testify/require"
8+
)
9+
10+
func TestKVStore(t *testing.T) {
11+
pairs := map[string]string{"key1": "value1", "key2": "value2", "key3": "value3"}
12+
13+
// Alice identity
14+
id, err := NewIdentityFromSr25519Phrase("//Alice")
15+
require.NoError(t, err)
16+
17+
sub := startLocalConnection(t)
18+
defer sub.Close()
19+
20+
t.Run("kvstore set values", func(t *testing.T) {
21+
for key, val := range pairs {
22+
err = sub.KVStoreSet(id, key, val)
23+
assert.NoError(t, err)
24+
}
25+
})
26+
27+
t.Run("kvstore get values", func(t *testing.T) {
28+
for key, val := range pairs {
29+
value, err := sub.KVStoreGet(id, key)
30+
assert.NoError(t, err)
31+
assert.Equal(t, []byte(val), value)
32+
}
33+
})
34+
35+
t.Run("kvstore list keys", func(t *testing.T) {
36+
values, err := sub.KVStoreList(id)
37+
assert.NoError(t, err)
38+
assert.EqualValues(t, values, pairs)
39+
})
40+
41+
t.Run("kvstore delete", func(t *testing.T) {
42+
for key := range pairs {
43+
err = sub.KVStoreDelete(id, key)
44+
assert.NoError(t, err)
45+
}
46+
47+
values, err := sub.KVStoreList(id)
48+
assert.NoError(t, err)
49+
assert.Empty(t, values)
50+
})
51+
}

clients/tfchain-client-go/service_contract_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func TestServiceContract(t *testing.T) {
8585
})
8686

8787
// 4. Bill consumer for service contract
88-
// should be able to go to future block to test varaible amount greater than 0
88+
// should be able to go to future block to test variable amount greater than 0
8989

9090
err = cl.ServiceContractBill(serviceIdentity, serviceContractID, variableAmount, billMetadata)
9191
require.NoError(t, err)

clients/tfchain-client-go/utils.go

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -335,7 +335,6 @@ func (s *Substrate) sign(e *types.Extrinsic, signer Identity, o types.SignatureO
335335
}
336336

337337
signerPubKey, err := types.NewMultiAddressFromAccountID(signer.PublicKey())
338-
339338
if err != nil {
340339
return err
341340
}
@@ -346,7 +345,6 @@ func (s *Substrate) sign(e *types.Extrinsic, signer Identity, o types.SignatureO
346345
}
347346

348347
sig, err := signer.Sign(b)
349-
350348
if err != nil {
351349
return err
352350
}
@@ -412,7 +410,7 @@ func (s *Substrate) CallOnce(cl Conn, meta Meta, identity Identity, call types.C
412410
return hash, err
413411
}
414412

415-
//node.Address =identity.PublicKey
413+
// node.Address =identity.PublicKey
416414
account, err := s.getAccount(cl, meta, identity)
417415
if err != nil {
418416
return hash, errors.Wrap(err, "failed to get account")
@@ -536,15 +534,37 @@ func (s *Substrate) checkForError(callResponse *CallResponse) error {
536534
if *accId == who {
537535
if int(e.DispatchError.ModuleError.Index) < len(moduleErrors) {
538536
if int(errIndex) >= len(moduleErrors[e.DispatchError.ModuleError.Index]) || moduleErrors[e.DispatchError.ModuleError.Index] == nil {
539-
return fmt.Errorf("module error (%d) with unknown code %d occured, please update the module error list", e.DispatchError.ModuleError.Index, e.DispatchError.ModuleError.Error)
537+
return fmt.Errorf("module error (%d) with unknown code %d occurred, please update the module error list", e.DispatchError.ModuleError.Index, e.DispatchError.ModuleError.Error)
540538
}
541539
return errors.New(moduleErrors[e.DispatchError.ModuleError.Index][errIndex])
542540
} else {
543-
return fmt.Errorf("unknown module error (%d) with code %d occured, please create the module error list", e.DispatchError.ModuleError.Index, e.DispatchError.ModuleError.Error)
541+
return fmt.Errorf("unknown module error (%d) with code %d occurred, please create the module error list", e.DispatchError.ModuleError.Index, e.DispatchError.ModuleError.Error)
544542
}
545543
}
546544
}
547545
}
548546

549547
return nil
550548
}
549+
550+
// decodeSecondKey extracts and decodes the second key(Vec<u8>) from the storage key.
551+
func decodeSecondKey(storageKey types.StorageKey, identity Identity) (key []byte, err error) {
552+
// remove 16 bytes(32 in hex) pallet and map prefixes.
553+
// pallet prefix (8 bytes): twox64(pallet_name)
554+
// map prefix (8bytes) twox64(map_name)
555+
prefixLen := 32
556+
557+
// the storage key contains two keys (AccountID and Vec<u8>)
558+
// remove the length of the first key(AccountID)
559+
// the hasher `Blake2_128Concat` includes a 16-byte hash followed by the AccountID
560+
firstKeyLen := 32 + len(identity.PublicKey())
561+
562+
offset := prefixLen + firstKeyLen
563+
564+
if len(storageKey) < offset {
565+
return nil, errors.New(fmt.Sprintf("failed to decode second key, storage key len should not be less than %d bytes", offset))
566+
}
567+
568+
err = Decode(storageKey[offset:], &key)
569+
return key, err
570+
}

0 commit comments

Comments
 (0)