11package tsrocksdb
22
33import (
4+ "bytes"
45 "encoding/binary"
56 "errors"
67 "fmt"
@@ -38,6 +39,9 @@ func init() {
3839type 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
4347func 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+
6169func (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
8896func (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
130150func (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
143155func (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+
219329func newTSReadOptions (version * int64 ) * grocksdb.ReadOptions {
220330 var ver uint64
221331 if version == nil {
0 commit comments