Skip to content

Commit cdde0e0

Browse files
author
Bartłomiej Święcki
committed
fix(client/file_cache): Fix storing immudb state in file cache
Signed-off-by: Bartłomiej Święcki <[email protected]>
1 parent 9646c44 commit cdde0e0

File tree

2 files changed

+158
-105
lines changed

2 files changed

+158
-105
lines changed

pkg/client/cache/file_cache.go

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,14 @@ import (
2020
"bufio"
2121
"bytes"
2222
"encoding/base64"
23-
"github.com/codenotary/immudb/pkg/api/schema"
24-
"github.com/golang/protobuf/proto"
25-
"github.com/rogpeppe/go-internal/lockedfile"
2623
"io"
2724
"os"
2825
"path/filepath"
2926
"strings"
27+
28+
"github.com/codenotary/immudb/pkg/api/schema"
29+
"github.com/golang/protobuf/proto"
30+
"github.com/rogpeppe/go-internal/lockedfile"
3031
)
3132

3233
// STATE_FN ...
@@ -54,22 +55,25 @@ func (w *fileCache) Get(serverUUID string, db string) (*schema.ImmutableState, e
5455
scanner.Split(bufio.ScanLines)
5556
for scanner.Scan() {
5657
line := scanner.Text()
57-
if strings.Contains(line, db+":") {
58-
r := strings.Split(line, ":")
59-
if r[1] == "" {
60-
return nil, ErrPrevStateNotFound
61-
}
62-
oldState, err := base64.StdEncoding.DecodeString(r[1])
63-
if err != nil {
64-
return nil, ErrLocalStateCorrupted
65-
}
66-
67-
state := &schema.ImmutableState{}
68-
if err = proto.Unmarshal(oldState, state); err != nil {
69-
return nil, ErrLocalStateCorrupted
70-
}
71-
return state, nil
58+
if !strings.HasPrefix(line, db+":") {
59+
continue
7260
}
61+
62+
oldState, err := base64.StdEncoding.DecodeString(line[len(db)+1:])
63+
if err != nil {
64+
return nil, ErrLocalStateCorrupted
65+
}
66+
67+
if len(oldState) == 0 {
68+
return nil, ErrLocalStateCorrupted
69+
}
70+
71+
state := &schema.ImmutableState{}
72+
if err = proto.Unmarshal(oldState, state); err != nil {
73+
return nil, ErrLocalStateCorrupted
74+
}
75+
76+
return state, nil
7377
}
7478
return nil, ErrPrevStateNotFound
7579
}
@@ -95,16 +99,23 @@ func (w *fileCache) Set(serverUUID string, db string, state *schema.ImmutableSta
9599
var lines [][]byte
96100
for scanner.Scan() {
97101
line := scanner.Text()
98-
if strings.Contains(line, db+":") {
102+
if strings.HasPrefix(line, db+":") {
99103
exists = true
100104
lines = append(lines, []byte(newState))
105+
} else {
106+
lines = append(lines, []byte(line))
101107
}
102108
}
103109
if !exists {
104110
lines = append(lines, []byte(newState))
105111
}
106112
output := bytes.Join(lines, []byte("\n"))
107113

114+
_, err = w.stateFile.Seek(0, io.SeekStart)
115+
if err != nil {
116+
return err
117+
}
118+
108119
err = w.stateFile.Truncate(0)
109120
if err != nil {
110121
return err

pkg/client/cache/file_cache_test.go

Lines changed: 128 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -2,178 +2,220 @@ package cache
22

33
import (
44
"encoding/base64"
5-
"github.com/codenotary/immudb/pkg/api/schema"
6-
"github.com/stretchr/testify/require"
5+
"fmt"
76
"io/ioutil"
8-
"log"
97
"os"
108
"testing"
9+
10+
"github.com/codenotary/immudb/pkg/api/schema"
11+
"github.com/stretchr/testify/require"
1112
)
1213

1314
func TestNewFileCache(t *testing.T) {
1415
dirname, err := ioutil.TempDir("", "example")
15-
if err != nil {
16-
log.Fatal(err)
17-
}
18-
os.Mkdir(dirname, os.ModePerm)
16+
require.NoError(t, err)
17+
defer os.RemoveAll(dirname)
18+
1919
fc := NewFileCache(dirname)
2020
require.IsType(t, &fileCache{}, fc)
21-
os.RemoveAll(dirname)
2221
}
2322

24-
func TestFileCacheSetError(t *testing.T) {
23+
func TestFileCacheSetErrorNotLocked(t *testing.T) {
2524
dirname, err := ioutil.TempDir("", "example")
26-
if err != nil {
27-
log.Fatal(err)
28-
}
25+
require.NoError(t, err)
26+
defer os.RemoveAll(dirname)
27+
2928
fc := NewFileCache(dirname)
3029
err = fc.Set("uuid", "dbName", &schema.ImmutableState{
3130
TxId: 0,
3231
TxHash: []byte(`hash`),
3332
Signature: nil,
3433
})
35-
require.Error(t, err)
36-
os.RemoveAll(dirname)
34+
require.ErrorIs(t, err, ErrCacheNotLocked)
3735
}
3836

3937
func TestFileCacheSet(t *testing.T) {
4038
dirname, err := ioutil.TempDir("", "example")
41-
if err != nil {
42-
log.Fatal(err)
43-
}
39+
require.NoError(t, err)
40+
defer os.RemoveAll(dirname)
41+
4442
fc := NewFileCache(dirname)
4543
err = fc.Lock("uuid")
4644
require.NoError(t, err)
45+
defer fc.Unlock()
46+
4747
err = fc.Set("uuid", "dbName", &schema.ImmutableState{
4848
TxId: 0,
4949
TxHash: []byte(`hash`),
5050
Signature: nil,
5151
})
5252
require.NoError(t, err)
53-
os.RemoveAll(dirname)
5453
}
5554

5655
func TestFileCacheGet(t *testing.T) {
5756
dirname, err := ioutil.TempDir("", "example")
58-
if err != nil {
59-
log.Fatal(err)
60-
}
57+
require.NoError(t, err)
58+
defer os.RemoveAll(dirname)
59+
6160
fc := NewFileCache(dirname)
6261
err = fc.Lock("uuid")
6362
require.NoError(t, err)
63+
defer fc.Unlock()
64+
6465
err = fc.Set("uuid", "dbName", &schema.ImmutableState{
6566
TxId: 0,
6667
TxHash: []byte(`hash`),
6768
Signature: nil,
6869
})
69-
require.Nil(t, err)
70-
_, err = fc.Get("uuid", "dbName")
71-
require.Nil(t, err)
72-
os.RemoveAll(dirname)
70+
require.NoError(t, err)
71+
72+
st, err := fc.Get("uuid", "dbName")
73+
require.NoError(t, err)
74+
75+
require.Equal(t, []byte(`hash`), st.TxHash)
7376
}
7477

75-
func TestFileCacheGetFail(t *testing.T) {
78+
func TestFileCacheGetFailNotLocked(t *testing.T) {
7679
dirname, err := ioutil.TempDir("", "example")
77-
if err != nil {
78-
log.Fatal(err)
79-
}
80+
require.NoError(t, err)
81+
defer os.RemoveAll(dirname)
82+
8083
fc := NewFileCache(dirname)
8184
_, err = fc.Get("uuid", "dbName")
82-
require.Error(t, err)
83-
os.RemoveAll(dirname)
85+
86+
require.ErrorIs(t, err, ErrCacheNotLocked)
8487
}
8588

8689
func TestFileCacheGetSingleLineError(t *testing.T) {
8790
dirname, err := ioutil.TempDir("", "example")
88-
if err != nil {
89-
log.Fatal(err)
90-
}
91-
f, err := os.Create(dirname + "/.state-test")
92-
if err != nil {
93-
log.Fatal("Cannot create state file", err)
94-
return
95-
}
91+
require.NoError(t, err)
92+
defer os.RemoveAll(dirname)
9693

9794
dbName := "dbt"
9895

99-
defer os.Remove(f.Name())
100-
if _, err = f.Write([]byte(dbName + ":")); err != nil {
101-
log.Fatal("Failed to write to temporary file", err)
102-
}
96+
err = ioutil.WriteFile(dirname+"/.state-test", []byte(dbName+":"), 0666)
97+
require.NoError(t, err)
10398

10499
fc := NewFileCache(dirname)
100+
err = fc.Lock("test")
101+
require.NoError(t, err)
102+
defer fc.Unlock()
103+
105104
_, err = fc.Get("test", dbName)
106-
require.Error(t, err)
107-
os.RemoveAll(dirname)
105+
require.ErrorIs(t, err, ErrLocalStateCorrupted)
108106
}
109107

110108
func TestFileCacheGetRootUnableToDecodeErr(t *testing.T) {
111109
dirname, err := ioutil.TempDir("", "example")
112-
if err != nil {
113-
log.Fatal(err)
114-
}
115-
f, err := os.Create(dirname + "/.state-test")
116-
if err != nil {
117-
log.Fatal("Cannot create state file", err)
118-
return
119-
}
110+
require.NoError(t, err)
111+
defer os.RemoveAll(dirname)
120112

121113
dbName := "dbt"
122114

123-
defer os.Remove(f.Name())
124-
if _, err = f.Write([]byte(dbName + ":firstLine")); err != nil {
125-
log.Fatal("Failed to write to temporary file", err)
126-
}
115+
err = ioutil.WriteFile(dirname+"/.state-test", []byte(dbName+":firstLine"), 0666)
116+
require.NoError(t, err)
127117

128118
fc := NewFileCache(dirname)
119+
err = fc.Lock("test")
120+
require.NoError(t, err)
121+
defer fc.Unlock()
122+
129123
_, err = fc.Get("test", dbName)
130-
require.Error(t, err)
131-
os.RemoveAll(dirname)
124+
require.ErrorIs(t, err, ErrLocalStateCorrupted)
132125
}
133126

134127
func TestFileCacheGetRootUnmarshalErr(t *testing.T) {
135128
dirname, err := ioutil.TempDir("", "example")
136-
if err != nil {
137-
log.Fatal(err)
138-
}
139-
f, err := os.Create(dirname + "/.state-test")
140-
if err != nil {
141-
log.Fatal("Cannot create state file", err)
142-
return
143-
}
129+
require.NoError(t, err)
130+
defer os.RemoveAll(dirname)
144131

145132
dbName := "dbt"
146133

147-
defer os.Remove(f.Name())
148-
if _, err = f.Write([]byte(dbName + ":" + base64.StdEncoding.EncodeToString([]byte("wrong-content")))); err != nil {
149-
log.Fatal("Failed to write to temporary file", err)
150-
}
134+
err = ioutil.WriteFile(dirname+"/.state-test", []byte(dbName+":"+base64.StdEncoding.EncodeToString([]byte("wrong-content"))), 0666)
135+
require.NoError(t, err)
151136

152137
fc := NewFileCache(dirname)
138+
err = fc.Lock("test")
139+
require.NoError(t, err)
140+
defer fc.Unlock()
141+
153142
_, err = fc.Get("test", dbName)
154-
require.Error(t, err)
155-
os.RemoveAll(dirname)
143+
require.ErrorIs(t, err, ErrLocalStateCorrupted)
156144
}
157145

158146
func TestFileCacheGetEmptyFile(t *testing.T) {
159147
dirname, err := ioutil.TempDir("", "example")
160-
if err != nil {
161-
log.Fatal(err)
162-
}
163-
f, err := os.Create(dirname + "/.state-test")
164-
if err != nil {
165-
log.Fatal("Cannot create state file", err)
166-
return
167-
}
148+
require.NoError(t, err)
149+
defer os.RemoveAll(dirname)
168150

169151
dbName := "dbt"
170152

171-
defer os.Remove(f.Name())
172-
if _, err = f.Write([]byte("")); err != nil {
173-
log.Fatal("Failed to write to temporary file", err)
174-
}
153+
err = ioutil.WriteFile(dirname+"/.state-test", []byte(""), 0666)
154+
require.NoError(t, err)
175155

176156
fc := NewFileCache(dirname)
157+
err = fc.Lock("test")
158+
require.NoError(t, err)
159+
defer fc.Unlock()
160+
177161
_, err = fc.Get("test", dbName)
178-
require.Error(t, err)
162+
require.ErrorIs(t, err, ErrPrevStateNotFound)
163+
}
164+
165+
func TestFileCacheOverwriteHash(t *testing.T) {
166+
dirname, err := ioutil.TempDir("", "example")
167+
require.NoError(t, err)
168+
defer os.RemoveAll(dirname)
169+
170+
fc := NewFileCache(dirname)
171+
err = fc.Lock("test")
172+
require.NoError(t, err)
173+
defer fc.Unlock()
174+
175+
err = fc.Set("test", "db1", &schema.ImmutableState{TxHash: []byte("hash1")})
176+
require.NoError(t, err)
177+
178+
st, err := fc.Get("test", "db1")
179+
require.NoError(t, err)
180+
require.Equal(t, []byte("hash1"), st.TxHash)
181+
182+
err = fc.Set("test", "db1", &schema.ImmutableState{TxHash: []byte("hash2")})
183+
require.NoError(t, err)
184+
185+
st, err = fc.Get("test", "db1")
186+
require.NoError(t, err)
187+
require.Equal(t, []byte("hash2"), st.TxHash)
188+
}
189+
190+
func TestFileCacheMultipleDatabases(t *testing.T) {
191+
dirname, err := ioutil.TempDir("", "example")
192+
require.NoError(t, err)
193+
defer os.RemoveAll(dirname)
194+
195+
fc := NewFileCache(dirname)
196+
err = fc.Lock("test")
197+
require.NoError(t, err)
198+
defer fc.Unlock()
199+
200+
for i := 0; i < 1000; i++ {
201+
202+
db := fmt.Sprintf("db%d", i)
203+
hash := []byte(fmt.Sprintf("hash%d", i))
204+
205+
err = fc.Set("test", db, &schema.ImmutableState{TxHash: hash})
206+
require.NoError(t, err)
207+
208+
st, err := fc.Get("test", db)
209+
require.NoError(t, err)
210+
require.Equal(t, hash, st.TxHash)
211+
}
212+
213+
for i := 0; i < 1000; i++ {
214+
db := fmt.Sprintf("db%d", i)
215+
hash := []byte(fmt.Sprintf("hash%d", i))
216+
217+
st, err := fc.Get("test", db)
218+
require.NoError(t, err)
219+
require.Equal(t, hash, st.TxHash)
220+
}
179221
}

0 commit comments

Comments
 (0)