Skip to content

Commit 6dc645a

Browse files
yihuangmmsqe
andauthored
Problem: no command to fix corrupted data in versiondb (#1685)
* Problem: no command to fix corrupted data in versiondb Closes: #1683 Solution: - add fix command to fix corrupted data in versiondb * rename * support SkipVersionZero * support SkipVersionZero * cleanup * cleanup * cleanup * destroy * fix data manually * Update CHANGELOG.md Signed-off-by: yihuang <[email protected]> * Update versiondb/tsrocksdb/store.go Co-authored-by: mmsqe <[email protected]> Signed-off-by: yihuang <[email protected]> * cli * cleanup * Update versiondb/client/fixdata.go Signed-off-by: yihuang <[email protected]> * rename * fix test * check nil * don't return nil as empty slice * add dryrun mode * cleanup * separete read from iteration * store name flag * validate timestamp * skip non-zero version * update gomod2nix * fix build * flush after fix * fix * revert --------- Signed-off-by: yihuang <[email protected]> Co-authored-by: mmsqe <[email protected]>
1 parent 0c5e998 commit 6dc645a

File tree

9 files changed

+321
-21
lines changed

9 files changed

+321
-21
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Bug Fixes
66

77
* [#1679](https://github.com/crypto-org-chain/cronos/pull/1679) Include no trace detail on insufficient balance fix.
8+
* [#1685](https://github.com/crypto-org-chain/cronos/pull/1685) Add command to fix versiondb corrupted data.
89

910
### Improvements
1011

app/versiondb.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ func (app *App) setupVersionDB(
3434
for _, key := range keys {
3535
exposedKeys = append(exposedKeys, key)
3636
}
37+
38+
// see: https://github.com/crypto-org-chain/cronos/issues/1683
39+
versionDB.SetSkipVersionZero(true)
40+
3741
app.CommitMultiStore().AddListeners(exposedKeys)
3842

3943
// register in app streaming manager

gomod2nix.toml

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

versiondb/client/cmd.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ func ChangeSetGroupCmd(opts Options) *cobra.Command {
2828
ChangeSetToVersionDBCmd(),
2929
RestoreAppDBCmd(opts),
3030
RestoreVersionDBCmd(),
31+
FixDataCmd(opts.DefaultStores),
3132
)
3233
return cmd
3334
}

versiondb/client/fixdata.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package client
2+
3+
import (
4+
"github.com/crypto-org-chain/cronos/versiondb/tsrocksdb"
5+
"github.com/linxGnu/grocksdb"
6+
"github.com/spf13/cobra"
7+
)
8+
9+
const (
10+
FlagDryRun = "dry-run"
11+
FlagStore = "store-name"
12+
)
13+
14+
func FixDataCmd(defaultStores []string) *cobra.Command {
15+
cmd := &cobra.Command{
16+
Use: "fixdata <dir>",
17+
Args: cobra.ExactArgs(1),
18+
Short: "Fix wrong data in versiondb, see: https://github.com/crypto-org-chain/cronos/issues/1683",
19+
RunE: func(cmd *cobra.Command, args []string) error {
20+
dir := args[0]
21+
dryRun, err := cmd.Flags().GetBool(FlagDryRun)
22+
if err != nil {
23+
return err
24+
}
25+
stores, err := cmd.Flags().GetStringArray(FlagStore)
26+
if err != nil {
27+
return err
28+
}
29+
if len(stores) == 0 {
30+
stores = defaultStores
31+
}
32+
33+
var (
34+
db *grocksdb.DB
35+
cfHandle *grocksdb.ColumnFamilyHandle
36+
)
37+
38+
if dryRun {
39+
db, cfHandle, err = tsrocksdb.OpenVersionDBForReadOnly(dir, false)
40+
} else {
41+
db, cfHandle, err = tsrocksdb.OpenVersionDB(dir)
42+
}
43+
if err != nil {
44+
return err
45+
}
46+
47+
versionDB := tsrocksdb.NewStoreWithDB(db, cfHandle)
48+
if err := versionDB.FixData(stores, dryRun); err != nil {
49+
return err
50+
}
51+
52+
return nil
53+
},
54+
}
55+
56+
cmd.Flags().Bool(FlagDryRun, false, "Dry run, do not write to the database, open the database in read-only mode.")
57+
cmd.Flags().StringArray(FlagStore, []string{}, "Store names to fix, if not specified, all stores will be fixed.")
58+
return cmd
59+
}

versiondb/tsrocksdb/iterator.go

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package tsrocksdb
22

33
import (
44
"bytes"
5+
"encoding/binary"
56

67
"github.com/crypto-org-chain/cronos/versiondb"
78
"github.com/linxGnu/grocksdb"
@@ -12,11 +13,14 @@ type rocksDBIterator struct {
1213
prefix, start, end []byte
1314
isReverse bool
1415
isInvalid bool
16+
17+
// see: https://github.com/crypto-org-chain/cronos/issues/1683
18+
skipVersionZero bool
1519
}
1620

1721
var _ versiondb.Iterator = (*rocksDBIterator)(nil)
1822

19-
func newRocksDBIterator(source *grocksdb.Iterator, prefix, start, end []byte, isReverse bool) *rocksDBIterator {
23+
func newRocksDBIterator(source *grocksdb.Iterator, prefix, start, end []byte, isReverse bool, skipVersionZero bool) *rocksDBIterator {
2024
if isReverse {
2125
if end == nil {
2226
source.SeekToLast()
@@ -39,14 +43,18 @@ func newRocksDBIterator(source *grocksdb.Iterator, prefix, start, end []byte, is
3943
source.Seek(start)
4044
}
4145
}
42-
return &rocksDBIterator{
43-
source: source,
44-
prefix: prefix,
45-
start: start,
46-
end: end,
47-
isReverse: isReverse,
48-
isInvalid: false,
46+
it := &rocksDBIterator{
47+
source: source,
48+
prefix: prefix,
49+
start: start,
50+
end: end,
51+
isReverse: isReverse,
52+
isInvalid: false,
53+
skipVersionZero: skipVersionZero,
4954
}
55+
56+
it.trySkipZeroVersion()
57+
return it
5058
}
5159

5260
// Domain implements Iterator.
@@ -120,6 +128,22 @@ func (itr rocksDBIterator) Next() {
120128
} else {
121129
itr.source.Next()
122130
}
131+
132+
itr.trySkipZeroVersion()
133+
}
134+
135+
func (itr rocksDBIterator) timestamp() uint64 {
136+
ts := itr.source.Timestamp()
137+
defer ts.Free()
138+
return binary.LittleEndian.Uint64(ts.Data())
139+
}
140+
141+
func (itr rocksDBIterator) trySkipZeroVersion() {
142+
if itr.skipVersionZero {
143+
for itr.Valid() && itr.timestamp() == 0 {
144+
itr.Next()
145+
}
146+
}
123147
}
124148

125149
// Error implements Iterator.

versiondb/tsrocksdb/opts.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@ func OpenVersionDB(dir string) (*grocksdb.DB, *grocksdb.ColumnFamilyHandle, erro
6464
return db, cfHandles[1], nil
6565
}
6666

67+
// OpenVersionDBForReadOnly open versiondb in readonly mode
68+
func OpenVersionDBForReadOnly(dir string, errorIfWalFileExists bool) (*grocksdb.DB, *grocksdb.ColumnFamilyHandle, error) {
69+
opts := grocksdb.NewDefaultOptions()
70+
db, cfHandles, err := grocksdb.OpenDbForReadOnlyColumnFamilies(
71+
opts, dir, []string{"default", VersionDBCFName},
72+
[]*grocksdb.Options{opts, NewVersionDBOpts(false)},
73+
errorIfWalFileExists,
74+
)
75+
if err != nil {
76+
return nil, nil, err
77+
}
78+
return db, cfHandles[1], nil
79+
}
80+
6781
// OpenVersionDBAndTrimHistory opens versiondb similar to `OpenVersionDB`,
6882
// but it also trim the versions newer than target one, can be used for rollback.
6983
func OpenVersionDBAndTrimHistory(dir string, version int64) (*grocksdb.DB, *grocksdb.ColumnFamilyHandle, error) {

versiondb/tsrocksdb/store.go

Lines changed: 122 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package tsrocksdb
22

33
import (
4+
"bytes"
45
"encoding/binary"
56
"errors"
67
"fmt"
@@ -38,6 +39,9 @@ func init() {
3839
type Store struct {
3940
db *grocksdb.DB
4041
cfHandle *grocksdb.ColumnFamilyHandle
42+
43+
// see: https://github.com/crypto-org-chain/cronos/issues/1683
44+
skipVersionZero bool
4145
}
4246

4347
func NewStore(dir string) (Store, error) {
@@ -58,6 +62,10 @@ func NewStoreWithDB(db *grocksdb.DB, cfHandle *grocksdb.ColumnFamilyHandle) Stor
5862
}
5963
}
6064

65+
func (s *Store) SetSkipVersionZero(skip bool) {
66+
s.skipVersionZero = skip
67+
}
68+
6169
func (s Store) SetLatestVersion(version int64) error {
6270
var ts [TimestampSize]byte
6371
binary.LittleEndian.PutUint64(ts[:], uint64(version))
@@ -86,11 +94,23 @@ func (s Store) PutAtVersion(version int64, changeSet []*types.StoreKVPair) error
8694
}
8795

8896
func (s Store) GetAtVersionSlice(storeKey string, key []byte, version *int64) (*grocksdb.Slice, error) {
89-
return s.db.GetCF(
97+
value, ts, err := s.db.GetCFWithTS(
9098
newTSReadOptions(version),
9199
s.cfHandle,
92100
prependStoreKey(storeKey, key),
93101
)
102+
if err != nil {
103+
return nil, err
104+
}
105+
defer ts.Free()
106+
107+
if value.Exists() && s.skipVersionZero {
108+
if binary.LittleEndian.Uint64(ts.Data()) == 0 {
109+
return grocksdb.NewSlice(nil, 0), nil
110+
}
111+
}
112+
113+
return value, err
94114
}
95115

96116
// GetAtVersion implements VersionStore interface
@@ -128,28 +148,24 @@ func (s Store) GetLatestVersion() (int64, error) {
128148

129149
// IteratorAtVersion implements VersionStore interface
130150
func (s Store) IteratorAtVersion(storeKey string, start, end []byte, version *int64) (versiondb.Iterator, error) {
131-
if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
132-
return nil, errKeyEmpty
133-
}
134-
135-
prefix := storePrefix(storeKey)
136-
start, end = iterateWithPrefix(prefix, start, end)
137-
138-
itr := s.db.NewIteratorCF(newTSReadOptions(version), s.cfHandle)
139-
return newRocksDBIterator(itr, prefix, start, end, false), nil
151+
return s.iteratorAtVersion(storeKey, start, end, version, false)
140152
}
141153

142154
// ReverseIteratorAtVersion implements VersionStore interface
143155
func (s Store) ReverseIteratorAtVersion(storeKey string, start, end []byte, version *int64) (versiondb.Iterator, error) {
156+
return s.iteratorAtVersion(storeKey, start, end, version, true)
157+
}
158+
159+
func (s Store) iteratorAtVersion(storeKey string, start, end []byte, version *int64, reverse bool) (versiondb.Iterator, error) {
144160
if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) {
145161
return nil, errKeyEmpty
146162
}
147163

148164
prefix := storePrefix(storeKey)
149-
start, end = iterateWithPrefix(storePrefix(storeKey), start, end)
165+
start, end = iterateWithPrefix(prefix, start, end)
150166

151167
itr := s.db.NewIteratorCF(newTSReadOptions(version), s.cfHandle)
152-
return newRocksDBIterator(itr, prefix, start, end, true), nil
168+
return newRocksDBIterator(itr, prefix, start, end, reverse, s.skipVersionZero), nil
153169
}
154170

155171
// FeedChangeSet is used to migrate legacy change sets into versiondb
@@ -216,6 +232,100 @@ func (s Store) Flush() error {
216232
)
217233
}
218234

235+
// FixData fixes wrong data written in versiondb due to rocksdb upgrade, the operation is idempotent.
236+
// see: https://github.com/crypto-org-chain/cronos/issues/1683
237+
// call this before `SetSkipVersionZero(true)`.
238+
func (s Store) FixData(storeNames []string, dryRun bool) error {
239+
for _, storeName := range storeNames {
240+
if err := s.fixDataStore(storeName, dryRun); err != nil {
241+
return err
242+
}
243+
}
244+
245+
return s.Flush()
246+
}
247+
248+
// fixDataStore iterate the wrong data at version 0, parse the timestamp from the key and write it again.
249+
func (s Store) fixDataStore(storeName string, dryRun bool) error {
250+
pairs, err := s.loadWrongData(storeName)
251+
if err != nil {
252+
return err
253+
}
254+
255+
batch := grocksdb.NewWriteBatch()
256+
defer batch.Destroy()
257+
258+
prefix := storePrefix(storeName)
259+
readOpts := grocksdb.NewDefaultReadOptions()
260+
defer readOpts.Destroy()
261+
for _, pair := range pairs {
262+
realKey := cloneAppend(prefix, pair.Key)
263+
264+
readOpts.SetTimestamp(pair.Timestamp)
265+
oldValue, err := s.db.GetCF(readOpts, s.cfHandle, realKey)
266+
if err != nil {
267+
return err
268+
}
269+
270+
clean := bytes.Equal(oldValue.Data(), pair.Value)
271+
oldValue.Free()
272+
273+
if clean {
274+
continue
275+
}
276+
277+
if dryRun {
278+
fmt.Printf("fix data: %s, key: %X, ts: %X\n", storeName, pair.Key, pair.Timestamp)
279+
} else {
280+
batch.PutCFWithTS(s.cfHandle, realKey, pair.Timestamp, pair.Value)
281+
}
282+
}
283+
284+
if !dryRun {
285+
return s.db.Write(defaultSyncWriteOpts, batch)
286+
}
287+
288+
return nil
289+
}
290+
291+
type KVPairWithTS struct {
292+
Key []byte
293+
Value []byte
294+
Timestamp []byte
295+
}
296+
297+
func (s Store) loadWrongData(storeName string) ([]KVPairWithTS, error) {
298+
var version int64
299+
iter, err := s.IteratorAtVersion(storeName, nil, nil, &version)
300+
if err != nil {
301+
return nil, err
302+
}
303+
defer iter.Close()
304+
305+
var pairs []KVPairWithTS
306+
for ; iter.Valid(); iter.Next() {
307+
if binary.LittleEndian.Uint64(iter.Timestamp()) != 0 {
308+
// FIXME: https://github.com/crypto-org-chain/cronos/issues/1689
309+
continue
310+
}
311+
312+
key := iter.Key()
313+
if len(key) < TimestampSize {
314+
return nil, fmt.Errorf("invalid key length: %X, store: %s", key, storeName)
315+
}
316+
317+
ts := key[len(key)-TimestampSize:]
318+
key = key[:len(key)-TimestampSize]
319+
pairs = append(pairs, KVPairWithTS{
320+
Key: key,
321+
Value: iter.Value(),
322+
Timestamp: ts,
323+
})
324+
}
325+
326+
return pairs, nil
327+
}
328+
219329
func newTSReadOptions(version *int64) *grocksdb.ReadOptions {
220330
var ver uint64
221331
if version == nil {

0 commit comments

Comments
 (0)