Skip to content
This repository was archived by the owner on Apr 4, 2023. It is now read-only.

Commit 76f5747

Browse files
committed
vm/storage: add prefix helper functions
Signed-off-by: Gyuho Lee <[email protected]>
1 parent 8de9bcd commit 76f5747

File tree

3 files changed

+188
-5
lines changed

3 files changed

+188
-5
lines changed

vm/storage/prefix.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package storage
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
)
7+
8+
var (
9+
delimiter = byte(0x2f) // '/'
10+
noPrefixEnd = []byte{0}
11+
)
12+
13+
var (
14+
ErrInvalidKeyDelimiter = errors.New("key has unexpected delimiters; only flat key or sub-key is supported")
15+
)
16+
17+
// getPrefix returns the prefixed key and range query end key for list calls.
18+
// put "foo" becomes "foo/" for its own namespace, and range ends with "foo0"
19+
// put "fop" becomes "fop/" for its own namespace, and range ends with "fop0"
20+
// put "foo1" becomes "foo1/" for its own namespace, and range ends with "foo10"
21+
func getPrefix(key []byte) (pfx []byte, sub []byte, end []byte, err error) {
22+
if bytes.Count(key, []byte{delimiter}) > 1 {
23+
return nil, nil, nil, ErrInvalidKeyDelimiter
24+
}
25+
26+
idx := bytes.IndexRune(key, rune(delimiter))
27+
switch {
28+
case idx == -1: // "foo"
29+
pfx = append(key, delimiter)
30+
case idx == len(key)-1: // "foo/"
31+
pfx, sub = key, nil
32+
default: // "a/b"
33+
ss := bytes.Split(key, []byte{delimiter})
34+
pfx, sub = append(ss[0], delimiter), ss[1]
35+
}
36+
37+
// next lexicographical key (range end) for prefix queries
38+
end = make([]byte, len(pfx))
39+
copy(end, pfx)
40+
for i := len(end) - 1; i >= 0; i-- {
41+
if end[i] < 0xff {
42+
end[i] = end[i] + 1
43+
end = end[:i+1]
44+
return pfx, sub, end, nil
45+
}
46+
}
47+
// next prefix does not exist (e.g., 0xffff);
48+
// default to special end key
49+
return pfx, sub, noPrefixEnd, nil
50+
}

vm/storage/prefix_test.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package storage
2+
3+
import (
4+
"bytes"
5+
"testing"
6+
)
7+
8+
func TestPrefix(t *testing.T) {
9+
tt := []struct {
10+
key []byte
11+
pfx []byte
12+
sub []byte
13+
end []byte
14+
err error
15+
}{
16+
{
17+
key: []byte("foo"),
18+
pfx: []byte("foo/"),
19+
sub: nil,
20+
end: []byte("foo0"),
21+
err: nil,
22+
},
23+
{
24+
key: []byte("foo/"),
25+
pfx: []byte("foo/"),
26+
sub: nil,
27+
end: []byte("foo0"),
28+
err: nil,
29+
},
30+
{
31+
key: []byte("a/b"),
32+
pfx: []byte("a/"),
33+
sub: []byte("b"),
34+
end: []byte("a0"),
35+
err: nil,
36+
},
37+
{
38+
key: []byte("a/b/"),
39+
pfx: nil,
40+
sub: nil,
41+
end: nil,
42+
err: ErrInvalidKeyDelimiter,
43+
},
44+
}
45+
for i, tv := range tt {
46+
pfx, sub, end, err := getPrefix(tv.key)
47+
if !bytes.Equal(pfx, tv.pfx) {
48+
t.Fatalf("#%d: pfx expected %q, got %q", i, string(tv.pfx), string(pfx))
49+
}
50+
if !bytes.Equal(sub, tv.sub) {
51+
t.Fatalf("#%d: sub expected %q, got %q", i, string(tv.sub), string(sub))
52+
}
53+
if !bytes.Equal(end, tv.end) {
54+
t.Fatalf("#%d: end expected %q, got %q", i, string(tv.end), string(end))
55+
}
56+
if err != tv.err {
57+
t.Fatalf("#%d: err expected %v, got %v", i, tv.err, err)
58+
}
59+
}
60+
}

vm/storage/storage.go

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,28 +4,101 @@
44
package storage
55

66
import (
7+
"errors"
8+
79
"github.com/ava-labs/avalanchego/database"
810
"github.com/ava-labs/avalanchego/database/prefixdb"
911
"github.com/ava-labs/avalanchego/database/versiondb"
12+
"github.com/ava-labs/quarkvm/crypto/ed25519"
13+
)
14+
15+
var (
16+
blockStatePrefix = []byte("block")
17+
18+
// (singleton prefix info)
19+
infoPrefix = []byte("info")
20+
// (tx hashes)
21+
txPrefix = []byte("tx")
22+
// (prefix keys)
23+
keyPrefix = []byte("key")
1024
)
1125

1226
type Storage interface {
13-
Block() *prefixdb.Database
27+
Block() database.Database
28+
Info() database.Database
29+
Tx() database.Database
30+
Key() database.Database
31+
32+
// Put writes key-value pair with prefix checks.
33+
Put(k []byte, v []byte, opts ...OpOption) error
1434
}
1535

1636
type storage struct {
1737
baseDB *versiondb.Database
1838
blockDB *prefixdb.Database
39+
infoDB *prefixdb.Database
40+
txDB *prefixdb.Database
41+
keyDB *prefixdb.Database
1942
}
2043

21-
var (
22-
blockStatePrefix = []byte("block")
23-
)
24-
2544
func New(db database.Database) Storage {
2645
baseDB := versiondb.New(db)
2746
return &storage{
2847
baseDB: baseDB,
2948
blockDB: prefixdb.New(blockStatePrefix, baseDB),
49+
infoDB: prefixdb.New(infoPrefix, baseDB),
50+
txDB: prefixdb.New(txPrefix, baseDB),
51+
keyDB: prefixdb.New(keyPrefix, baseDB),
52+
}
53+
}
54+
55+
func (s *storage) Block() database.Database {
56+
return s.blockDB
57+
}
58+
59+
func (s *storage) Info() database.Database {
60+
return s.infoDB
61+
}
62+
63+
func (s *storage) Tx() database.Database {
64+
return s.txDB
65+
}
66+
67+
func (s *storage) Key() database.Database {
68+
return s.keyDB
69+
}
70+
71+
func (s *storage) Put(k []byte, v []byte, opts ...OpOption) error {
72+
ret := &Op{}
73+
ret.applyOpts(opts)
74+
if ret.overwrite && ret.owner == nil {
75+
return errors.New("caller must provide the public key to make modification or overwrite")
3076
}
77+
78+
// TODO: look up key
79+
// if exists, only overwrite when conditions are met
80+
// implement other business logic
81+
82+
return s.keyDB.Put(k, v)
83+
}
84+
85+
type Op struct {
86+
overwrite bool
87+
owner ed25519.PublicKey
88+
}
89+
90+
type OpOption func(*Op)
91+
92+
func (op *Op) applyOpts(opts []OpOption) {
93+
for _, opt := range opts {
94+
opt(op)
95+
}
96+
}
97+
98+
func WithOverwrite(b bool) OpOption {
99+
return func(op *Op) { op.overwrite = b }
100+
}
101+
102+
func WithOwner(owner ed25519.PublicKey) OpOption {
103+
return func(op *Op) { op.owner = owner }
31104
}

0 commit comments

Comments
 (0)