diff --git a/CHANGELOG.md b/CHANGELOG.md index d36d777951..0b5d406386 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ * [#1670](https://github.com/crypto-org-chain/cronos/pull/1670) Fix state overwrite in debug trace APIs. * [#1679](https://github.com/crypto-org-chain/cronos/pull/1679) Include no trace detail on insufficient balance fix. +* [#1685](https://github.com/crypto-org-chain/cronos/pull/1685) Add command to fix versiondb corrupted data. +* [#1688](https://github.com/crypto-org-chain/cronos/pull/1688) Add timestamp api to versiondb iterator. +* [#1686](https://github.com/crypto-org-chain/cronos/pull/1686) Update rocksdb to 9.7.4. *Oct 14, 2024* diff --git a/app/versiondb.go b/app/versiondb.go index 10b0ad76ad..381db1027b 100644 --- a/app/versiondb.go +++ b/app/versiondb.go @@ -23,6 +23,7 @@ func (app *App) setupVersionDB( if err := os.MkdirAll(dataDir, os.ModePerm); err != nil { return nil, err } + versionDB, err := tsrocksdb.NewStore(dataDir) if err != nil { return nil, err @@ -34,6 +35,9 @@ func (app *App) setupVersionDB( exposeStoreKeys = append(exposeStoreKeys, storeKey) } + // see: https://github.com/crypto-org-chain/cronos/issues/1683 + versionDB.SetSkipVersionZero(true) + service := versiondb.NewStreamingService(versionDB, exposeStoreKeys) app.SetStreamingService(service) diff --git a/go.mod b/go.mod index 386ce5014e..cc60d20c95 100644 --- a/go.mod +++ b/go.mod @@ -27,7 +27,7 @@ require ( github.com/golang/protobuf v1.5.4 github.com/gorilla/mux v1.8.0 github.com/grpc-ecosystem/grpc-gateway v1.16.0 - github.com/linxGnu/grocksdb v1.9.2 + github.com/linxGnu/grocksdb v1.9.7 github.com/peggyjv/gravity-bridge/module/v2 v2.0.0-20220420162017-838c0d25e974 github.com/spf13/cast v1.6.0 github.com/spf13/cobra v1.8.0 diff --git a/go.sum b/go.sum index 2231f62066..d48b77ff32 100644 --- a/go.sum +++ b/go.sum @@ -1085,8 +1085,8 @@ github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6 github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/linxGnu/grocksdb v1.9.2 h1:O3mzvO0wuzQ9mtlHbDrShixyVjVbmuqTjFrzlf43wZ8= -github.com/linxGnu/grocksdb v1.9.2/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA= +github.com/linxGnu/grocksdb v1.9.7 h1:Bp2r1Yti/IXxEobZZnDooXAui/Q+5gVqgQMenLWyDUw= +github.com/linxGnu/grocksdb v1.9.7/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA= github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lyft/protoc-gen-star v0.5.3/go.mod h1:V0xaHgaf5oCCqmcxYcWiDfTiKsZsRc87/1qhoTACD8w= diff --git a/gomod2nix.toml b/gomod2nix.toml index 345984dccd..1cff51efb6 100644 --- a/gomod2nix.toml +++ b/gomod2nix.toml @@ -403,8 +403,8 @@ schema = 3 version = "v0.1.0" hash = "sha256-wQqGTtRWsfR9n0O/SXHVgECebbnNmHddxJIbG63OJBQ=" [mod."github.com/linxGnu/grocksdb"] - version = "v1.9.2" - hash = "sha256-ThXtaXx6LvRIFW4xLHsMrVWdsN2qobLPA0InLmlADOM=" + version = "v1.9.7" + hash = "sha256-ZSomnYZRo7gHB9/FW55MebNkNzn0DuR96RfsVpAwjIQ=" [mod."github.com/magiconair/properties"] version = "v1.8.7" hash = "sha256-XQ2bnc2s7/IH3WxEO4GishZurMyKwEclZy1DXg+2xXc=" diff --git a/nix/rocksdb.nix b/nix/rocksdb.nix index 66e37dd642..32e1e0720e 100644 --- a/nix/rocksdb.nix +++ b/nix/rocksdb.nix @@ -21,13 +21,13 @@ stdenv.mkDerivation (finalAttrs: { pname = "rocksdb"; - version = "9.2.1"; + version = "9.7.4"; src = fetchFromGitHub { owner = "facebook"; repo = finalAttrs.pname; rev = "v${finalAttrs.version}"; - hash = "sha256-Zifn5Gu/4h6TaEqSaWQ2mFdryeAarqbHWW3fKUGGFac="; + hash = "sha256-u5uuShM2SxHc9/zL4UU56IhCcR/ZQbzde0LgOYS44bM="; }; nativeBuildInputs = [ diff --git a/versiondb/client/cmd.go b/versiondb/client/cmd.go index 4bf36343e8..5955037f71 100644 --- a/versiondb/client/cmd.go +++ b/versiondb/client/cmd.go @@ -28,6 +28,7 @@ func ChangeSetGroupCmd(opts Options) *cobra.Command { ChangeSetToVersionDBCmd(), RestoreAppDBCmd(opts), RestoreVersionDBCmd(), + FixDataCmd(opts.DefaultStores), ) return cmd } diff --git a/versiondb/client/fixdata.go b/versiondb/client/fixdata.go new file mode 100644 index 0000000000..efd95fbff4 --- /dev/null +++ b/versiondb/client/fixdata.go @@ -0,0 +1,59 @@ +package client + +import ( + "github.com/crypto-org-chain/cronos/versiondb/tsrocksdb" + "github.com/linxGnu/grocksdb" + "github.com/spf13/cobra" +) + +const ( + FlagDryRun = "dry-run" + FlagStore = "store-name" +) + +func FixDataCmd(defaultStores []string) *cobra.Command { + cmd := &cobra.Command{ + Use: "fixdata ", + Args: cobra.ExactArgs(1), + Short: "Fix wrong data in versiondb, see: https://github.com/crypto-org-chain/cronos/issues/1683", + RunE: func(cmd *cobra.Command, args []string) error { + dir := args[0] + dryRun, err := cmd.Flags().GetBool(FlagDryRun) + if err != nil { + return err + } + stores, err := cmd.Flags().GetStringArray(FlagStore) + if err != nil { + return err + } + if len(stores) == 0 { + stores = defaultStores + } + + var ( + db *grocksdb.DB + cfHandle *grocksdb.ColumnFamilyHandle + ) + + if dryRun { + db, cfHandle, err = tsrocksdb.OpenVersionDBForReadOnly(dir, false) + } else { + db, cfHandle, err = tsrocksdb.OpenVersionDB(dir) + } + if err != nil { + return err + } + + versionDB := tsrocksdb.NewStoreWithDB(db, cfHandle) + if err := versionDB.FixData(stores, dryRun); err != nil { + return err + } + + return nil + }, + } + + cmd.Flags().Bool(FlagDryRun, false, "Dry run, do not write to the database, open the database in read-only mode.") + cmd.Flags().StringArray(FlagStore, []string{}, "Store names to fix, if not specified, all stores will be fixed.") + return cmd +} diff --git a/versiondb/go.mod b/versiondb/go.mod index 9b1f0454f6..9fb00f47f5 100644 --- a/versiondb/go.mod +++ b/versiondb/go.mod @@ -14,7 +14,7 @@ require ( github.com/cosmos/iavl v0.21.0-alpha.1.0.20230904092046-df3db2d96583 github.com/crypto-org-chain/cronos/memiavl v0.0.3 github.com/golang/snappy v0.0.4 - github.com/linxGnu/grocksdb v1.9.2 + github.com/linxGnu/grocksdb v1.9.7 github.com/spf13/cast v1.5.0 github.com/spf13/cobra v1.6.1 github.com/stretchr/testify v1.9.0 diff --git a/versiondb/go.sum b/versiondb/go.sum index 5f08098e34..cb73e9e5a9 100644 --- a/versiondb/go.sum +++ b/versiondb/go.sum @@ -515,8 +515,8 @@ github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6 github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/linxGnu/grocksdb v1.9.2 h1:O3mzvO0wuzQ9mtlHbDrShixyVjVbmuqTjFrzlf43wZ8= -github.com/linxGnu/grocksdb v1.9.2/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA= +github.com/linxGnu/grocksdb v1.9.7 h1:Bp2r1Yti/IXxEobZZnDooXAui/Q+5gVqgQMenLWyDUw= +github.com/linxGnu/grocksdb v1.9.7/go.mod h1:QYiYypR2d4v63Wj1adOOfzglnoII0gLj3PNh4fZkcFA= github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= diff --git a/versiondb/tsrocksdb/iterator.go b/versiondb/tsrocksdb/iterator.go index f4c4b3c777..9cb15e8a62 100644 --- a/versiondb/tsrocksdb/iterator.go +++ b/versiondb/tsrocksdb/iterator.go @@ -2,8 +2,9 @@ package tsrocksdb import ( "bytes" + "encoding/binary" - "github.com/cosmos/cosmos-sdk/store/types" + "github.com/crypto-org-chain/cronos/versiondb" "github.com/linxGnu/grocksdb" ) @@ -12,11 +13,14 @@ type rocksDBIterator struct { prefix, start, end []byte isReverse bool isInvalid bool + + // see: https://github.com/crypto-org-chain/cronos/issues/1683 + skipVersionZero bool } -var _ types.Iterator = (*rocksDBIterator)(nil) +var _ versiondb.Iterator = (*rocksDBIterator)(nil) -func newRocksDBIterator(source *grocksdb.Iterator, prefix, start, end []byte, isReverse bool) *rocksDBIterator { +func newRocksDBIterator(source *grocksdb.Iterator, prefix, start, end []byte, isReverse bool, skipVersionZero bool) *rocksDBIterator { if isReverse { if end == nil { source.SeekToLast() @@ -39,14 +43,18 @@ func newRocksDBIterator(source *grocksdb.Iterator, prefix, start, end []byte, is source.Seek(start) } } - return &rocksDBIterator{ - source: source, - prefix: prefix, - start: start, - end: end, - isReverse: isReverse, - isInvalid: false, + it := &rocksDBIterator{ + source: source, + prefix: prefix, + start: start, + end: end, + isReverse: isReverse, + isInvalid: false, + skipVersionZero: skipVersionZero, } + + it.trySkipZeroVersion() + return it } // Domain implements Iterator. @@ -114,6 +122,21 @@ func (itr rocksDBIterator) Next() { } else { itr.source.Next() } + + itr.trySkipZeroVersion() +} + +func (itr rocksDBIterator) Timestamp() []byte { + itr.assertIsValid() + return moveSliceToBytes(itr.source.Timestamp()) +} + +func (itr rocksDBIterator) trySkipZeroVersion() { + if itr.skipVersionZero { + for itr.Valid() && binary.LittleEndian.Uint64(itr.Timestamp()) == 0 { + itr.Next() + } + } } // Error implements Iterator. diff --git a/versiondb/tsrocksdb/opts.go b/versiondb/tsrocksdb/opts.go index 604c203b72..8218cd9692 100644 --- a/versiondb/tsrocksdb/opts.go +++ b/versiondb/tsrocksdb/opts.go @@ -64,6 +64,20 @@ func OpenVersionDB(dir string) (*grocksdb.DB, *grocksdb.ColumnFamilyHandle, erro return db, cfHandles[1], nil } +// OpenVersionDBForReadOnly open versiondb in readonly mode +func OpenVersionDBForReadOnly(dir string, errorIfWalFileExists bool) (*grocksdb.DB, *grocksdb.ColumnFamilyHandle, error) { + opts := grocksdb.NewDefaultOptions() + db, cfHandles, err := grocksdb.OpenDbForReadOnlyColumnFamilies( + opts, dir, []string{"default", VersionDBCFName}, + []*grocksdb.Options{opts, NewVersionDBOpts(false)}, + errorIfWalFileExists, + ) + if err != nil { + return nil, nil, err + } + return db, cfHandles[1], nil +} + // OpenVersionDBAndTrimHistory opens versiondb similar to `OpenVersionDB`, // but it also trim the versions newer than target one, can be used for rollback. func OpenVersionDBAndTrimHistory(dir string, version int64) (*grocksdb.DB, *grocksdb.ColumnFamilyHandle, error) { diff --git a/versiondb/tsrocksdb/store.go b/versiondb/tsrocksdb/store.go index 7d4176c5d0..706c357b98 100644 --- a/versiondb/tsrocksdb/store.go +++ b/versiondb/tsrocksdb/store.go @@ -1,6 +1,7 @@ package tsrocksdb import ( + "bytes" "encoding/binary" "errors" "fmt" @@ -38,6 +39,9 @@ func init() { type Store struct { db *grocksdb.DB cfHandle *grocksdb.ColumnFamilyHandle + + // see: https://github.com/crypto-org-chain/cronos/issues/1683 + skipVersionZero bool } func NewStore(dir string) (Store, error) { @@ -58,6 +62,10 @@ func NewStoreWithDB(db *grocksdb.DB, cfHandle *grocksdb.ColumnFamilyHandle) Stor } } +func (s *Store) SetSkipVersionZero(skip bool) { + s.skipVersionZero = skip +} + func (s Store) SetLatestVersion(version int64) error { var ts [TimestampSize]byte binary.LittleEndian.PutUint64(ts[:], uint64(version)) @@ -86,11 +94,23 @@ func (s Store) PutAtVersion(version int64, changeSet []types.StoreKVPair) error } func (s Store) GetAtVersionSlice(storeKey string, key []byte, version *int64) (*grocksdb.Slice, error) { - return s.db.GetCF( + value, ts, err := s.db.GetCFWithTS( newTSReadOptions(version), s.cfHandle, prependStoreKey(storeKey, key), ) + if err != nil { + return nil, err + } + defer ts.Free() + + if value.Exists() && s.skipVersionZero { + if binary.LittleEndian.Uint64(ts.Data()) == 0 { + return grocksdb.NewSlice(nil, 0), nil + } + } + + return value, err } // GetAtVersion implements VersionStore interface @@ -127,29 +147,25 @@ func (s Store) GetLatestVersion() (int64, error) { } // IteratorAtVersion implements VersionStore interface -func (s Store) IteratorAtVersion(storeKey string, start, end []byte, version *int64) (types.Iterator, error) { - if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { - return nil, errKeyEmpty - } - - prefix := storePrefix(storeKey) - start, end = iterateWithPrefix(prefix, start, end) - - itr := s.db.NewIteratorCF(newTSReadOptions(version), s.cfHandle) - return newRocksDBIterator(itr, prefix, start, end, false), nil +func (s Store) IteratorAtVersion(storeKey string, start, end []byte, version *int64) (versiondb.Iterator, error) { + return s.iteratorAtVersion(storeKey, start, end, version, false) } // ReverseIteratorAtVersion implements VersionStore interface -func (s Store) ReverseIteratorAtVersion(storeKey string, start, end []byte, version *int64) (types.Iterator, error) { +func (s Store) ReverseIteratorAtVersion(storeKey string, start, end []byte, version *int64) (versiondb.Iterator, error) { + return s.iteratorAtVersion(storeKey, start, end, version, true) +} + +func (s Store) iteratorAtVersion(storeKey string, start, end []byte, version *int64, reverse bool) (versiondb.Iterator, error) { if (start != nil && len(start) == 0) || (end != nil && len(end) == 0) { return nil, errKeyEmpty } prefix := storePrefix(storeKey) - start, end = iterateWithPrefix(storePrefix(storeKey), start, end) + start, end = iterateWithPrefix(prefix, start, end) itr := s.db.NewIteratorCF(newTSReadOptions(version), s.cfHandle) - return newRocksDBIterator(itr, prefix, start, end, true), nil + return newRocksDBIterator(itr, prefix, start, end, reverse, s.skipVersionZero), nil } // FeedChangeSet is used to migrate legacy change sets into versiondb @@ -216,6 +232,103 @@ func (s Store) Flush() error { ) } +// FixData fixes wrong data written in versiondb due to rocksdb upgrade, the operation is idempotent. +// see: https://github.com/crypto-org-chain/cronos/issues/1683 +// call this before `SetSkipVersionZero(true)`. +func (s Store) FixData(storeNames []string, dryRun bool) error { + for _, storeName := range storeNames { + if err := s.fixDataStore(storeName, dryRun); err != nil { + return err + } + } + + if !dryRun { + return s.Flush() + } + + return nil +} + +// fixDataStore iterate the wrong data at version 0, parse the timestamp from the key and write it again. +func (s Store) fixDataStore(storeName string, dryRun bool) error { + pairs, err := s.loadWrongData(storeName) + if err != nil { + return err + } + + batch := grocksdb.NewWriteBatch() + defer batch.Destroy() + + prefix := storePrefix(storeName) + readOpts := grocksdb.NewDefaultReadOptions() + defer readOpts.Destroy() + for _, pair := range pairs { + realKey := cloneAppend(prefix, pair.Key) + + readOpts.SetTimestamp(pair.Timestamp) + oldValue, err := s.db.GetCF(readOpts, s.cfHandle, realKey) + if err != nil { + return err + } + + clean := bytes.Equal(oldValue.Data(), pair.Value) + oldValue.Free() + + if clean { + continue + } + + if dryRun { + fmt.Printf("fix data: %s, key: %X, ts: %X\n", storeName, pair.Key, pair.Timestamp) + } else { + batch.PutCFWithTS(s.cfHandle, realKey, pair.Timestamp, pair.Value) + } + } + + if !dryRun { + return s.db.Write(defaultSyncWriteOpts, batch) + } + + return nil +} + +type KVPairWithTS struct { + Key []byte + Value []byte + Timestamp []byte +} + +func (s Store) loadWrongData(storeName string) ([]KVPairWithTS, error) { + var version int64 + iter, err := s.IteratorAtVersion(storeName, nil, nil, &version) + if err != nil { + return nil, err + } + defer iter.Close() + + var pairs []KVPairWithTS + for ; iter.Valid(); iter.Next() { + ts := iter.Timestamp() + if binary.LittleEndian.Uint64(ts) != 0 { + // FIXME: https://github.com/crypto-org-chain/cronos/issues/1689 + continue + } + + key := iter.Key() + if len(key) < TimestampSize { + return nil, fmt.Errorf("invalid key length: %X, store: %s", key, storeName) + } + + pairs = append(pairs, KVPairWithTS{ + Key: key[:len(key)-TimestampSize], + Timestamp: key[len(key)-TimestampSize:], + Value: iter.Value(), + }) + } + + return pairs, nil +} + func newTSReadOptions(version *int64) *grocksdb.ReadOptions { var ver uint64 if version == nil { diff --git a/versiondb/tsrocksdb/store_test.go b/versiondb/tsrocksdb/store_test.go index a5328977a6..f3ec3617fb 100644 --- a/versiondb/tsrocksdb/store_test.go +++ b/versiondb/tsrocksdb/store_test.go @@ -4,6 +4,8 @@ import ( "encoding/binary" "testing" + dbm "github.com/cometbft/cometbft-db" + "github.com/cosmos/cosmos-sdk/store/types" "github.com/crypto-org-chain/cronos/versiondb" "github.com/linxGnu/grocksdb" "github.com/stretchr/testify/require" @@ -153,3 +155,88 @@ func TestUserTimestampPruning(t *testing.T) { require.Equal(t, []byte{100}, bz.Data()) bz.Free() } + +func TestSkipVersionZero(t *testing.T) { + storeKey := "test" + + var wrongTz [8]byte + binary.LittleEndian.PutUint64(wrongTz[:], 100) + + key1 := []byte("hello1") + key2 := []byte("hello2") + key2Wrong := cloneAppend(key2, wrongTz[:]) + key3 := []byte("hello3") + + store, err := NewStore(t.TempDir()) + require.NoError(t, err) + + err = store.PutAtVersion(0, []types.StoreKVPair{ + {StoreKey: storeKey, Key: key2Wrong, Value: []byte{2}}, + }) + require.NoError(t, err) + err = store.PutAtVersion(100, []types.StoreKVPair{ + {StoreKey: storeKey, Key: key1, Value: []byte{1}}, + }) + require.NoError(t, err) + err = store.PutAtVersion(100, []types.StoreKVPair{ + {StoreKey: storeKey, Key: key3, Value: []byte{3}}, + }) + require.NoError(t, err) + + i := int64(999) + bz, err := store.GetAtVersion(storeKey, key2Wrong, &i) + require.NoError(t, err) + require.Equal(t, []byte{2}, bz) + + it, err := store.IteratorAtVersion(storeKey, nil, nil, &i) + require.NoError(t, err) + require.Equal(t, + []kvPair{ + {Key: key1, Value: []byte{1}}, + {Key: key2Wrong, Value: []byte{2}}, + {Key: key3, Value: []byte{3}}, + }, + consumeIterator(it), + ) + + store.SetSkipVersionZero(true) + + bz, err = store.GetAtVersion(storeKey, key2Wrong, &i) + require.NoError(t, err) + require.Empty(t, bz) + bz, err = store.GetAtVersion(storeKey, key1, &i) + require.NoError(t, err) + require.Equal(t, []byte{1}, bz) + + it, err = store.IteratorAtVersion(storeKey, nil, nil, &i) + require.NoError(t, err) + require.Equal(t, + []kvPair{ + {Key: key1, Value: []byte{1}}, + {Key: key3, Value: []byte{3}}, + }, + consumeIterator(it), + ) + + store.SetSkipVersionZero(false) + err = store.FixData([]string{storeKey}, false) + require.NoError(t, err) + + bz, err = store.GetAtVersion(storeKey, key2, &i) + require.NoError(t, err) + require.Equal(t, []byte{2}, bz) +} + +type kvPair struct { + Key []byte + Value []byte +} + +func consumeIterator(it dbm.Iterator) []kvPair { + var result []kvPair + for ; it.Valid(); it.Next() { + result = append(result, kvPair{it.Key(), it.Value()}) + } + it.Close() + return result +} diff --git a/versiondb/types.go b/versiondb/types.go index 6c69fbbf94..9f5eab3637 100644 --- a/versiondb/types.go +++ b/versiondb/types.go @@ -4,14 +4,20 @@ import ( "github.com/cosmos/cosmos-sdk/store/types" ) +type Iterator interface { + types.Iterator + + Timestamp() []byte +} + // VersionStore is a versioned storage of a flat key-value pairs. // it don't need to support merkle proof, so could be implemented in a much more efficient way. // `nil` version means the latest version. type VersionStore interface { GetAtVersion(storeKey string, key []byte, version *int64) ([]byte, error) HasAtVersion(storeKey string, key []byte, version *int64) (bool, error) - IteratorAtVersion(storeKey string, start, end []byte, version *int64) (types.Iterator, error) - ReverseIteratorAtVersion(storeKey string, start, end []byte, version *int64) (types.Iterator, error) + IteratorAtVersion(storeKey string, start, end []byte, version *int64) (Iterator, error) + ReverseIteratorAtVersion(storeKey string, start, end []byte, version *int64) (Iterator, error) GetLatestVersion() (int64, error) // Persist the change set of a block,